diff --git a/Engine/source/T3D/fx/ribbon.cpp b/Engine/source/T3D/fx/ribbon.cpp new file mode 100644 index 000000000..d035c3491 --- /dev/null +++ b/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( 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()); + + 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(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(); + 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 &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; +} \ No newline at end of file diff --git a/Engine/source/T3D/fx/ribbon.h b/Engine/source/T3D/fx/ribbon.h new file mode 100644 index 000000000..ce115d461 --- /dev/null +++ b/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 mSegmentPoints; ///< The points in space where the ribbon has spawned segments. + BaseMatInstance *mRibbonMat; + MaterialParameterHandle* mRadiusSC; + MaterialParameterHandle* mRibbonProjSC; + GFXPrimitiveBufferHandle primBuffer; + GFXVertexBufferHandle 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 &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 +