diff --git a/Engine/source/T3D/fx/ribbon.cpp b/Engine/source/T3D/fx/ribbon.cpp new file mode 100644 index 000000000..49150711e --- /dev/null +++ b/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( 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 < 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; +} \ 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..10ca8d40b --- /dev/null +++ b/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 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 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 &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 + diff --git a/Engine/source/T3D/fx/ribbonNode.cpp b/Engine/source/T3D/fx/ribbonNode.cpp new file mode 100644 index 000000000..2582fbe6f --- /dev/null +++ b/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( 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 ); +} diff --git a/Engine/source/T3D/fx/ribbonNode.h b/Engine/source/T3D/fx/ribbonNode.h new file mode 100644 index 000000000..cdd86ef33 --- /dev/null +++ b/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 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_ + diff --git a/Engine/source/gfx/gfxVertexTypes.cpp b/Engine/source/gfx/gfxVertexTypes.cpp index ca4598270..dbee17550 100644 --- a/Engine/source/gfx/gfxVertexTypes.cpp +++ b/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 ); diff --git a/Engine/source/gfx/gfxVertexTypes.h b/Engine/source/gfx/gfxVertexTypes.h index 9072f533c..8af285f5d 100644 --- a/Engine/source/gfx/gfxVertexTypes.h +++ b/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; diff --git a/Templates/Empty/game/art/ribbons/materials.cs b/Templates/Empty/game/art/ribbons/materials.cs new file mode 100644 index 000000000..d53ada9e2 --- /dev/null +++ b/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; +}; \ No newline at end of file diff --git a/Templates/Empty/game/art/ribbons/ribbonExec.cs b/Templates/Empty/game/art/ribbons/ribbonExec.cs new file mode 100644 index 000000000..8193b1b8b --- /dev/null +++ b/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"); \ No newline at end of file diff --git a/Templates/Empty/game/art/ribbons/ribbons.cs b/Templates/Empty/game/art/ribbons/ribbons.cs new file mode 100644 index 000000000..b2184b74c --- /dev/null +++ b/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; +}; diff --git a/Templates/Empty/game/core/scripts/server/game.cs b/Templates/Empty/game/core/scripts/server/game.cs index d80dd4468..c135e6f99 100644 --- a/Templates/Empty/game/core/scripts/server/game.cs +++ b/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" ); diff --git a/Templates/Empty/game/scripts/server/game.cs b/Templates/Empty/game/scripts/server/game.cs index 4826c0de2..d9529ca01 100644 --- a/Templates/Empty/game/scripts/server/game.cs +++ b/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" ); diff --git a/Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderP.hlsl b/Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderP.hlsl new file mode 100644 index 000000000..5527f20cc --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderV.hlsl b/Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderV.hlsl new file mode 100644 index 000000000..933fbc0eb --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/Templates/Empty/game/tools/worldEditor/gui/objectBuilderGui.ed.gui b/Templates/Empty/game/tools/worldEditor/gui/objectBuilderGui.ed.gui index 19c396a57..33cb5de75 100644 --- a/Templates/Empty/game/tools/worldEditor/gui/objectBuilderGui.ed.gui +++ b/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"; diff --git a/Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs b/Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs index d63542d67..75d41eb53 100644 --- a/Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs +++ b/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" ); diff --git a/Templates/Full/game/art/ribbons/materials.cs b/Templates/Full/game/art/ribbons/materials.cs new file mode 100644 index 000000000..a8ebc4a61 --- /dev/null +++ b/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; +}; \ No newline at end of file diff --git a/Templates/Full/game/art/ribbons/ribTex.png b/Templates/Full/game/art/ribbons/ribTex.png new file mode 100644 index 000000000..82dffb96a Binary files /dev/null and b/Templates/Full/game/art/ribbons/ribTex.png differ diff --git a/Templates/Full/game/art/ribbons/ribbonExec.cs b/Templates/Full/game/art/ribbons/ribbonExec.cs new file mode 100644 index 000000000..8193b1b8b --- /dev/null +++ b/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"); \ No newline at end of file diff --git a/Templates/Full/game/art/ribbons/ribbons.cs b/Templates/Full/game/art/ribbons/ribbons.cs new file mode 100644 index 000000000..8660ec4d2 --- /dev/null +++ b/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; +}; \ No newline at end of file diff --git a/Templates/Full/game/core/scripts/server/game.cs b/Templates/Full/game/core/scripts/server/game.cs index d80dd4468..c135e6f99 100644 --- a/Templates/Full/game/core/scripts/server/game.cs +++ b/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" ); diff --git a/Templates/Full/game/scripts/server/game.cs b/Templates/Full/game/scripts/server/game.cs index ace3ac2f8..42d329cd2 100644 --- a/Templates/Full/game/scripts/server/game.cs +++ b/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" ); diff --git a/Templates/Full/game/shaders/common/ribbons/basicRibbonShaderP.hlsl b/Templates/Full/game/shaders/common/ribbons/basicRibbonShaderP.hlsl new file mode 100644 index 000000000..5527f20cc --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/ribbons/basicRibbonShaderV.hlsl b/Templates/Full/game/shaders/common/ribbons/basicRibbonShaderV.hlsl new file mode 100644 index 000000000..933fbc0eb --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/ribbons/texRibbonShaderP.hlsl b/Templates/Full/game/shaders/common/ribbons/texRibbonShaderP.hlsl new file mode 100644 index 000000000..ade2a1899 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/ribbons/texRibbonShaderV.hlsl b/Templates/Full/game/shaders/common/ribbons/texRibbonShaderV.hlsl new file mode 100644 index 000000000..933fbc0eb --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/Templates/Full/game/tools/worldEditor/gui/objectBuilderGui.ed.gui b/Templates/Full/game/tools/worldEditor/gui/objectBuilderGui.ed.gui index 19c396a57..33cb5de75 100644 --- a/Templates/Full/game/tools/worldEditor/gui/objectBuilderGui.ed.gui +++ b/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"; diff --git a/Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs b/Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs index d63542d67..75d41eb53 100644 --- a/Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs +++ b/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" );