mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
1838 lines
75 KiB
C++
1838 lines
75 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Written by Melvyn May, Started on 4th August 2002.
|
|
//
|
|
// "My code is written for the Torque community, so do your worst with it,
|
|
// just don't rip-it-off and call it your own without even thanking me".
|
|
//
|
|
// - Melv.
|
|
//
|
|
//
|
|
// Conversion to TSE By Brian "bzztbomb" Richardson 9/2005
|
|
// This was a neat piece of code! Thanks Melv!
|
|
// I've switched this to use one large indexed primitive buffer. All animation
|
|
// is then done in the vertex shader. This means we have a static vertex/primitive
|
|
// buffer that never changes! How spiff! Because of this, the culling code was
|
|
// changed to render out full quadtree nodes, we don't try to cull each individual
|
|
// node ourselves anymore. This means to get good performance, you probably need to do the
|
|
// following:
|
|
// 1. If it's a small area to cover, turn off culling completely.
|
|
// 2. You want to tune the parameters to make sure there are a lot of billboards within
|
|
// each quadrant.
|
|
//
|
|
// POTENTIAL TODO LIST:
|
|
// TODO: Clamp item alpha to fog alpha
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
|
|
// Copyright (C) 2015 Faust Logic, Inc.
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
#include "platform/platform.h"
|
|
#include "T3D/fx/fxFoliageReplicator.h"
|
|
|
|
#include "gfx/gfxDevice.h"
|
|
#include "gfx/primBuilder.h" // Used for debug / mission edit rendering
|
|
#include "console/consoleTypes.h"
|
|
#include "core/stream/bitStream.h"
|
|
#include "math/mRandom.h"
|
|
#include "math/mathIO.h"
|
|
#include "console/simBase.h"
|
|
#include "scene/sceneManager.h"
|
|
#include "renderInstance/renderPassManager.h"
|
|
#include "scene/sceneRenderState.h"
|
|
#include "sim/netConnection.h"
|
|
#include "materials/shaderData.h"
|
|
#include "console/engineAPI.h"
|
|
|
|
const U32 AlphaTexLen = 1024;
|
|
|
|
GFXImplementVertexFormat( GFXVertexFoliage )
|
|
{
|
|
addElement( "POSITION", GFXDeclType_Float3 );
|
|
addElement( "NORMAL", GFXDeclType_Float3 );
|
|
addElement( "TEXCOORD", GFXDeclType_Float2, 0 );
|
|
addElement( "TEXCOORD", GFXDeclType_Float2, 1 );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put the function in /example/common/editor/ObjectBuilderGui.gui [around line 458] ...
|
|
//
|
|
// function ObjectBuilderGui::buildfxFoliageReplicator(%this)
|
|
// {
|
|
// %this.className = "fxFoliageReplicator";
|
|
// %this.process();
|
|
// }
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put this in /example/common/editor/EditorGui.cs in [function Creator::init( %this )]
|
|
//
|
|
// %Environment_Item[8] = "fxFoliageReplicator"; <-- ADD THIS.
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put this in /example/common/client/missionDownload.cs in [function clientCmdMissionStartPhase3(%seq,%missionName)] (line 65)
|
|
// after codeline 'onPhase2Complete();'.
|
|
//
|
|
// StartFoliageReplication();
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put this in /engine/console/simBase.h (around line 509) in
|
|
//
|
|
// namespace Sim
|
|
// {
|
|
// DeclareNamedSet(fxFoliageSet) <-- ADD THIS (Note no semi-colon).
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put this in /engine/console/simBase.cc (around line 19) in
|
|
//
|
|
// ImplementNamedSet(fxFoliageSet) <-- ADD THIS (Note no semi-colon).
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put this in /engine/console/simManager.cc [function void init()] (around line 269).
|
|
//
|
|
// namespace Sim
|
|
// {
|
|
// InstantiateNamedSet(fxFoliageSet); <-- ADD THIS (Including Semi-colon).
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
extern bool gEditingMission;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(fxFoliageReplicator);
|
|
|
|
ConsoleDocClass( fxFoliageReplicator,
|
|
"@brief An emitter to replicate fxFoliageItem objects across an area.\n"
|
|
"@ingroup Foliage\n"
|
|
);
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Trig Table Lookups.
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
const F32 PeriodLenMinus = (F32) (2.0f * M_PI) - 0.01f;
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Class: fxFoliageRenderList
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageRenderList::SetupClipPlanes( SceneRenderState* state, const F32 farClipPlane )
|
|
{
|
|
const F32 nearPlane = state->getNearPlane();
|
|
const F32 farPlane = farClipPlane;
|
|
|
|
const Frustum& frustum = state->getCullingFrustum();
|
|
|
|
// [rene, 23-Feb-11] Why isn't this preserving the ortho state of the original frustum?
|
|
|
|
mFrustum.set( false,//zoneState.frustum.isOrtho(),
|
|
frustum.getNearLeft(),
|
|
frustum.getNearRight(),
|
|
frustum.getNearTop(),
|
|
frustum.getNearBottom(),
|
|
nearPlane,
|
|
farPlane,
|
|
frustum.getTransform()
|
|
);
|
|
|
|
|
|
mBox = mFrustum.getBounds();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
inline void fxFoliageRenderList::DrawQuadBox(const Box3F& QuadBox, const LinearColorF Colour)
|
|
{
|
|
// Define our debug box.
|
|
static Point3F BoxPnts[] = {
|
|
Point3F(0,0,0),
|
|
Point3F(0,0,1),
|
|
Point3F(0,1,0),
|
|
Point3F(0,1,1),
|
|
Point3F(1,0,0),
|
|
Point3F(1,0,1),
|
|
Point3F(1,1,0),
|
|
Point3F(1,1,1)
|
|
};
|
|
|
|
static U32 BoxVerts[][4] = {
|
|
{0,2,3,1}, // -x
|
|
{7,6,4,5}, // +x
|
|
{0,1,5,4}, // -y
|
|
{3,2,6,7}, // +y
|
|
{0,4,6,2}, // -z
|
|
{3,7,5,1} // +z
|
|
};
|
|
|
|
// Project our Box Points.
|
|
Point3F ProjectionPoints[8];
|
|
|
|
for( U32 i=0; i<8; i++ )
|
|
{
|
|
ProjectionPoints[i].set(BoxPnts[i].x ? QuadBox.maxExtents.x : QuadBox.minExtents.x,
|
|
BoxPnts[i].y ? QuadBox.maxExtents.y : QuadBox.minExtents.y,
|
|
BoxPnts[i].z ? (mHeightLerp * QuadBox.maxExtents.z) + (1-mHeightLerp) * QuadBox.minExtents.z : QuadBox.minExtents.z);
|
|
|
|
}
|
|
|
|
PrimBuild::color(Colour);
|
|
|
|
// Draw the Box.
|
|
for(U32 x = 0; x < 6; x++)
|
|
{
|
|
// Draw a line-loop.
|
|
PrimBuild::begin(GFXLineStrip, 5);
|
|
|
|
for(U32 y = 0; y < 4; y++)
|
|
{
|
|
PrimBuild::vertex3f(ProjectionPoints[BoxVerts[x][y]].x,
|
|
ProjectionPoints[BoxVerts[x][y]].y,
|
|
ProjectionPoints[BoxVerts[x][y]].z);
|
|
}
|
|
PrimBuild::vertex3f(ProjectionPoints[BoxVerts[x][0]].x,
|
|
ProjectionPoints[BoxVerts[x][0]].y,
|
|
ProjectionPoints[BoxVerts[x][0]].z);
|
|
PrimBuild::end();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool fxFoliageRenderList::IsQuadrantVisible(const Box3F VisBox, const MatrixF& RenderTransform)
|
|
{
|
|
// Can we trivially accept the visible box?
|
|
if ( !mFrustum.isCulled( VisBox ) )
|
|
return true;
|
|
|
|
// Not visible.
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Class: fxFoliageCulledList
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
fxFoliageCulledList::fxFoliageCulledList(Box3F SearchBox, fxFoliageCulledList* InVec)
|
|
{
|
|
// Find the Candidates.
|
|
FindCandidates(SearchBox, InVec);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageCulledList::FindCandidates(const Box3F& SearchBox, fxFoliageCulledList* InVec)
|
|
{
|
|
// Search the Culled List.
|
|
for (U32 i = 0; i < InVec->GetListCount(); i++)
|
|
{
|
|
// Is this Box overlapping our search box?
|
|
if (SearchBox.isOverlapped(InVec->GetElement(i)->FoliageBox))
|
|
{
|
|
// Yes, so add it to our culled list.
|
|
mCulledObjectSet.push_back(InVec->GetElement(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Class: fxFoliageReplicator
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
fxFoliageReplicator::fxFoliageReplicator()
|
|
{
|
|
// Setup NetObject.
|
|
mTypeMask |= StaticObjectType;
|
|
mNetFlags.set(Ghostable | ScopeAlways);
|
|
|
|
// Reset Client Replication Started.
|
|
mClientReplicationStarted = false;
|
|
|
|
// Reset Foliage Count.
|
|
mCurrentFoliageCount = 0;
|
|
|
|
dMemset(&mFrustumRenderSet, 0, sizeof(mFrustumRenderSet));
|
|
// Reset Creation Area Angle Animation.
|
|
mCreationAreaAngle = 0;
|
|
|
|
// Reset Last Render Time.
|
|
mLastRenderTime = 0;
|
|
|
|
// Reset Foliage Nodes.
|
|
mPotentialFoliageNodes = 0;
|
|
// Reset Billboards Acquired.
|
|
mBillboardsAcquired = 0;
|
|
|
|
// Reset Frame Serial ID.
|
|
mFrameSerialID = 0;
|
|
mQuadTreeLevels = 0;
|
|
mNextAllocatedNodeIdx = 0;
|
|
mAlphaLookup = NULL;
|
|
mFadeInGradient = 0.0f;
|
|
mFadeOutGradient = 0.0f;
|
|
mGlobalSwayPhase = 0.0f;
|
|
mGlobalSwayTimeRatio = 1.0f;
|
|
mGlobalLightPhase = 0.0f;
|
|
mGlobalLightTimeRatio = 1.0f;
|
|
|
|
mDirty = true;
|
|
|
|
mFoliageShaderProjectionSC = NULL;
|
|
mFoliageShaderWorldSC = NULL;
|
|
mFoliageShaderGlobalSwayPhaseSC = NULL;
|
|
mFoliageShaderSwayMagnitudeSideSC = NULL;
|
|
mFoliageShaderSwayMagnitudeFrontSC = NULL;
|
|
mFoliageShaderGlobalLightPhaseSC = NULL;
|
|
mFoliageShaderLuminanceMagnitudeSC = NULL;
|
|
mFoliageShaderLuminanceMidpointSC = NULL;
|
|
mFoliageShaderDistanceRangeSC = NULL;
|
|
mFoliageShaderCameraPosSC = NULL;
|
|
mFoliageShaderTrueBillboardSC = NULL;
|
|
mFoliageShaderGroundAlphaSC = NULL;
|
|
mFoliageShaderAmbientColorSC = NULL;
|
|
mDiffuseTextureSC = NULL;
|
|
mAlphaMapTextureSC = NULL;
|
|
|
|
mShaderData = NULL;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
fxFoliageReplicator::~fxFoliageReplicator()
|
|
{
|
|
if (mAlphaLookup)
|
|
delete mAlphaLookup;
|
|
|
|
mPlacementSB = NULL;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::initPersistFields()
|
|
{
|
|
// Add out own persistent fields.
|
|
addGroup( "Debugging" ); // MM: Added Group Header.
|
|
addField( "UseDebugInfo", TypeBool, Offset( mFieldData.mUseDebugInfo, fxFoliageReplicator ), "Culling bins are drawn when set to true." );
|
|
addField( "DebugBoxHeight", TypeF32, Offset( mFieldData.mDebugBoxHeight, fxFoliageReplicator ), "Height multiplier for drawn culling bins.");
|
|
addField( "HideFoliage", TypeBool, Offset( mFieldData.mHideFoliage, fxFoliageReplicator ), "Foliage is hidden when set to true." );
|
|
addField( "ShowPlacementArea", TypeBool, Offset( mFieldData.mShowPlacementArea, fxFoliageReplicator ), "Draw placement rings when set to true." );
|
|
addField( "PlacementAreaHeight", TypeS32, Offset( mFieldData.mPlacementBandHeight, fxFoliageReplicator ), "Height of the placement ring in world units." );
|
|
addField( "PlacementColour", TypeColorF, Offset( mFieldData.mPlaceAreaColour, fxFoliageReplicator ), "Color of the placement ring." );
|
|
endGroup( "Debugging" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "Media" ); // MM: Added Group Header.
|
|
addField( "Seed", TypeS32, Offset( mFieldData.mSeed, fxFoliageReplicator ), "Random seed for foliage placement." );
|
|
addField( "FoliageFile", TypeFilename, Offset( mFieldData.mFoliageFile, fxFoliageReplicator ), "Image file for the foliage texture." );
|
|
addField( "FoliageCount", TypeS32, Offset( mFieldData.mFoliageCount, fxFoliageReplicator ), "Maximum foliage instance count." );
|
|
addField( "FoliageRetries", TypeS32, Offset( mFieldData.mFoliageRetries, fxFoliageReplicator ), "Number of times to try placing a foliage instance before giving up." );
|
|
endGroup( "Media" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "Area" ); // MM: Added Group Header.
|
|
addField( "InnerRadiusX", TypeS32, Offset( mFieldData.mInnerRadiusX, fxFoliageReplicator ), "Placement area inner radius on the X axis" );
|
|
addField( "InnerRadiusY", TypeS32, Offset( mFieldData.mInnerRadiusY, fxFoliageReplicator ), "Placement area inner radius on the Y axis" );
|
|
addField( "OuterRadiusX", TypeS32, Offset( mFieldData.mOuterRadiusX, fxFoliageReplicator ), "Placement area outer radius on the X axis" );
|
|
addField( "OuterRadiusY", TypeS32, Offset( mFieldData.mOuterRadiusY, fxFoliageReplicator ), "Placement area outer radius on the Y axis" );
|
|
endGroup( "Area" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "Dimensions" ); // MM: Added Group Header.
|
|
addField( "MinWidth", TypeF32, Offset( mFieldData.mMinWidth, fxFoliageReplicator ), "Minimum width of foliage billboards" );
|
|
addField( "MaxWidth", TypeF32, Offset( mFieldData.mMaxWidth, fxFoliageReplicator ), "Maximum width of foliage billboards" );
|
|
addField( "MinHeight", TypeF32, Offset( mFieldData.mMinHeight, fxFoliageReplicator ), "Minimum height of foliage billboards" );
|
|
addField( "MaxHeight", TypeF32, Offset( mFieldData.mMaxHeight, fxFoliageReplicator ), "Maximum height of foliage billboards" );
|
|
addField( "FixAspectRatio", TypeBool, Offset( mFieldData.mFixAspectRatio, fxFoliageReplicator ), "Maintain aspect ratio of image if true. This option ignores MaxWidth." );
|
|
addField( "FixSizeToMax", TypeBool, Offset( mFieldData.mFixSizeToMax, fxFoliageReplicator ), "Use only MaxWidth and MaxHeight for billboard size. Ignores MinWidth and MinHeight." );
|
|
addField( "OffsetZ", TypeF32, Offset( mFieldData.mOffsetZ, fxFoliageReplicator ), "Offset billboards by this amount vertically." );
|
|
addField( "RandomFlip", TypeBool, Offset( mFieldData.mRandomFlip, fxFoliageReplicator ), "Randomly flip billboards left-to-right." );
|
|
addField( "UseTrueBillboards", TypeBool, Offset( mFieldData.mUseTrueBillboards, fxFoliageReplicator ), "Use camera facing billboards ( including the z axis )." );
|
|
endGroup( "Dimensions" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "Culling" ); // MM: Added Group Header.
|
|
addField( "UseCulling", TypeBool, Offset( mFieldData.mUseCulling, fxFoliageReplicator ), "Use culling bins when enabled." );
|
|
addField( "CullResolution", TypeS32, Offset( mFieldData.mCullResolution, fxFoliageReplicator ), "Minimum size of culling bins. Must be >= 8 and <= OuterRadius." );
|
|
addField( "ViewDistance", TypeF32, Offset( mFieldData.mViewDistance, fxFoliageReplicator ), "Maximum distance from camera where foliage appears." );
|
|
addField( "ViewClosest", TypeF32, Offset( mFieldData.mViewClosest, fxFoliageReplicator ), "Minimum distance from camera where foliage appears." );
|
|
addField( "FadeInRegion", TypeF32, Offset( mFieldData.mFadeInRegion, fxFoliageReplicator ), "Region beyond ViewDistance where foliage fades in/out." );
|
|
addField( "FadeOutRegion", TypeF32, Offset( mFieldData.mFadeOutRegion, fxFoliageReplicator ), "Region before ViewClosest where foliage fades in/out." );
|
|
addField( "AlphaCutoff", TypeF32, Offset( mFieldData.mAlphaCutoff, fxFoliageReplicator ), "Minimum alpha value allowed on foliage instances." );
|
|
addField( "GroundAlpha", TypeF32, Offset( mFieldData.mGroundAlpha, fxFoliageReplicator ), "Alpha of the foliage at ground level. 0 = transparent, 1 = opaque." );
|
|
endGroup( "Culling" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "Animation" ); // MM: Added Group Header.
|
|
addField( "SwayOn", TypeBool, Offset( mFieldData.mSwayOn, fxFoliageReplicator ), "Foliage should sway randomly when true." );
|
|
addField( "SwaySync", TypeBool, Offset( mFieldData.mSwaySync, fxFoliageReplicator ), "Foliage instances should sway together when true and SwayOn is enabled." );
|
|
addField( "SwayMagSide", TypeF32, Offset( mFieldData.mSwayMagnitudeSide, fxFoliageReplicator ), "Left-to-right sway magnitude." );
|
|
addField( "SwayMagFront", TypeF32, Offset( mFieldData.mSwayMagnitudeFront, fxFoliageReplicator ), "Front-to-back sway magnitude." );
|
|
addField( "MinSwayTime", TypeF32, Offset( mFieldData.mMinSwayTime, fxFoliageReplicator ), "Minumum sway cycle time in seconds." );
|
|
addField( "MaxSwayTime", TypeF32, Offset( mFieldData.mMaxSwayTime, fxFoliageReplicator ), "Maximum sway cycle time in seconds." );
|
|
endGroup( "Animation" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "Lighting" ); // MM: Added Group Header.
|
|
addField( "LightOn", TypeBool, Offset( mFieldData.mLightOn, fxFoliageReplicator ), "Foliage should be illuminated with changing lights when true." );
|
|
addField( "LightSync", TypeBool, Offset( mFieldData.mLightSync, fxFoliageReplicator ), "Foliage instances have the same lighting when set and LightOn is set." );
|
|
addField( "MinLuminance", TypeF32, Offset( mFieldData.mMinLuminance, fxFoliageReplicator ), "Minimum luminance for foliage instances." );
|
|
addField( "MaxLuminance", TypeF32, Offset( mFieldData.mMaxLuminance, fxFoliageReplicator ), "Maximum luminance for foliage instances." );
|
|
addField( "LightTime", TypeF32, Offset( mFieldData.mLightTime, fxFoliageReplicator ), "Time before foliage illumination cycle repeats." );
|
|
endGroup( "Lighting" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "Restrictions" ); // MM: Added Group Header.
|
|
addField( "AllowOnTerrain", TypeBool, Offset( mFieldData.mAllowOnTerrain, fxFoliageReplicator ), "Foliage will be placed on terrain when set." );
|
|
addField( "AllowOnStatics", TypeBool, Offset( mFieldData.mAllowStatics, fxFoliageReplicator ), "Foliage will be placed on Static shapes when set." );
|
|
addField( "AllowOnWater", TypeBool, Offset( mFieldData.mAllowOnWater, fxFoliageReplicator ), "Foliage will be placed on/under water when set." );
|
|
addField( "AllowWaterSurface", TypeBool, Offset( mFieldData.mAllowWaterSurface, fxFoliageReplicator ), "Foliage will be placed on water when set. Requires AllowOnWater." );
|
|
addField( "AllowedTerrainSlope", TypeS32, Offset( mFieldData.mAllowedTerrainSlope, fxFoliageReplicator ), "Maximum surface angle allowed for foliage instances." );
|
|
endGroup( "Restrictions" ); // MM: Added Group Footer.
|
|
|
|
addGroup( "AFX" );
|
|
addField( "AmbientModulationBias", TypeF32, Offset( mFieldData.mAmbientModulationBias,fxFoliageReplicator ), "Multiplier controling amount foliage is modulated by sun's ambient." );
|
|
endGroup( "AFX" );
|
|
// Initialise parents' persistent fields.
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::CreateFoliage(void)
|
|
{
|
|
F32 HypX, HypY;
|
|
F32 Angle;
|
|
U32 RelocationRetry;
|
|
Point3F FoliagePosition;
|
|
Point3F FoliageStart;
|
|
Point3F FoliageEnd;
|
|
Point3F FoliageScale;
|
|
bool CollisionResult;
|
|
RayInfo RayEvent;
|
|
|
|
// Let's get a minimum bounding volume.
|
|
Point3F MinPoint( -0.5, -0.5, -0.5 );
|
|
Point3F MaxPoint( 0.5, 0.5, 0.5 );
|
|
|
|
// Check Host.
|
|
AssertFatal(isClientObject(), "Trying to create Foliage on Server, this is bad!");
|
|
|
|
// Cannot continue without Foliage Texture!
|
|
if (dStrlen(mFieldData.mFoliageFile) == 0)
|
|
return;
|
|
|
|
// Check that we can position somewhere!
|
|
if (!( mFieldData.mAllowOnTerrain ||
|
|
mFieldData.mAllowStatics ||
|
|
mFieldData.mAllowOnWater))
|
|
{
|
|
// Problem ...
|
|
Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Could not place Foliage, All alloweds are off!");
|
|
|
|
// Return here.
|
|
return;
|
|
}
|
|
|
|
// Destroy Foliage if we've already got some.
|
|
if (mCurrentFoliageCount != 0) DestroyFoliage();
|
|
|
|
// Inform the user if culling has been disabled!
|
|
if (!mFieldData.mUseCulling)
|
|
{
|
|
// Console Output.
|
|
Con::printf("fxFoliageReplicator - Culling has been disabled!");
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
// > Calculate the Potential Foliage Nodes Required to achieve the selected culling resolution.
|
|
// > Populate Quad-tree structure to depth determined by culling resolution.
|
|
//
|
|
// A little explanation is called for here ...
|
|
//
|
|
// The approach to this problem has been choosen to make it *much* easier for
|
|
// the user to control the quad-tree culling resolution. The user enters a single
|
|
// world-space value 'mCullResolution' which controls the highest resolution at
|
|
// which the replicator will check visibility culling.
|
|
//
|
|
// example: If 'mCullResolution' is 32 and the size of the replicated area is 128 radius
|
|
// (256 diameter) then this results in the replicator creating a quad-tree where
|
|
// there are 256/32 = 8x8 blocks. Each of these can be checked to see if they
|
|
// reside within the viewing frustum and if not then they get culled therefore
|
|
// removing the need to parse all the billboards that occcupy that region.
|
|
// Most of the time you will get better than this as the culling algorithm will
|
|
// check the culling pyramid from the top to bottom e.g. the follow 'blocks'
|
|
// will be checked:-
|
|
//
|
|
// 1 x 256 x 256 (All of replicated area)
|
|
// 4 x 128 x 128 (4 corners of above)
|
|
// 16 x 64 x 64 (16 x 4 corners of above)
|
|
// etc.
|
|
//
|
|
//
|
|
// 1. First-up, the replicator needs to create a fixed-list of quad-tree nodes to work with.
|
|
//
|
|
// To calculate this we take the largest outer-radius value set in the replicator and
|
|
// calculate how many quad-tree levels are required to achieve the selected 'mCullResolution'.
|
|
// One of the initial problems is that the replicator has seperate radii values for X & Y.
|
|
// This can lead to a culling resolution smaller in one axis than the other if there is a
|
|
// difference between the Outer-Radii. Unfortunately, we just live with this as there is
|
|
// not much we can do here if we still want to allow the user to have this kind of
|
|
// elliptical placement control.
|
|
//
|
|
// To calculate the number of nodes needed we using the following equation:-
|
|
//
|
|
// Note:- We are changing the Logarithmic bases from 10 -> 2 ... grrrr!
|
|
//
|
|
// Cr = mCullResolution
|
|
// Rs = Maximum Radii Diameter
|
|
//
|
|
//
|
|
// ( Log10( Rs / Cr ) )
|
|
// int ( ---------------- + 0.5 )
|
|
// ( Log10( 2 ) )
|
|
//
|
|
// ---------|
|
|
// |
|
|
// | n
|
|
// / 4
|
|
// /
|
|
// ---------|
|
|
// n = 0
|
|
//
|
|
//
|
|
// So basically we calculate the number of blocks in 1D at the highest resolution, then
|
|
// calculate the inverse exponential (base 2 - 1D) to achieve that quantity of blocks.
|
|
// We round that upto the next highest integer = e. We then sum 4 to the power 0->e
|
|
// which gives us the correct number of nodes required. e is also stored as the starting
|
|
// level value for populating the quad-tree (see 3. below).
|
|
//
|
|
// 2. We then proceed to calculate the billboard positions as normal and calculate and assign
|
|
// each billboard a basic volume (rather than treat each as a point). We need to take into
|
|
// account possible front/back swaying as well as the basic plane dimensions here.
|
|
// When all the billboards have been choosen we then proceed to populate the quad-tree.
|
|
//
|
|
// 3. To populate the quad-tree we start with a box which completely encapsulates the volume
|
|
// occupied by all the billboards and enter into a recursive procedure to process that node.
|
|
// Processing this node involves splitting it into quadrants in X/Y untouched (for now).
|
|
// We then find candidate billboards with each of these quadrants searching using the
|
|
// current subset of shapes from the parent (this reduces the searching to a minimum and
|
|
// is very efficient).
|
|
//
|
|
// If a quadrant does not enclose any billboards then the node is dropped otherwise it
|
|
// is processed again using the same procedure.
|
|
//
|
|
// This happens until we have recursed through the maximum number of levels as calculated
|
|
// using the summation max (see equation above). When level 0 is reached, the current list
|
|
// of enclosed objects is stored within the node (for the rendering algorithm).
|
|
//
|
|
// 4. When this is complete we have finished here. The next stage is when rendering takes place.
|
|
// An algorithm steps through the quad-tree from the top and does visibility culling on
|
|
// each box (with respect to the viewing frustum) and culls as appropriate. If the box is
|
|
// visible then the next level is checked until we reach level 0 where the node contains
|
|
// a complete subset of billboards enclosed by the visible box.
|
|
//
|
|
//
|
|
// Using the above algorithm we can now generate *massive* quantities of billboards and (using the
|
|
// appropriate 'mCullResolution') only visible blocks of billboards will be processed.
|
|
//
|
|
// - Melv.
|
|
//
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
// Step 1.
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
|
|
// Calculate the maximum dimension.
|
|
F32 MaxDimension = 2.0f * ( (mFieldData.mOuterRadiusX > mFieldData.mOuterRadiusY) ? mFieldData.mOuterRadiusX : mFieldData.mOuterRadiusY );
|
|
|
|
// Let's check that our cull resolution is not greater than half our maximum dimension (and less than 1).
|
|
if (mFieldData.mCullResolution > (MaxDimension/2) || mFieldData.mCullResolution < 8)
|
|
{
|
|
// Problem ...
|
|
Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Could create Foliage, invalid Culling Resolution!");
|
|
Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Culling Resolution *must* be >=8 or <= %0.2f!", (MaxDimension/2));
|
|
|
|
// Return here.
|
|
return;
|
|
}
|
|
|
|
// Take first Timestamp.
|
|
F32 mStartCreationTime = (F32) Platform::getRealMilliseconds();
|
|
|
|
// Calculate the quad-tree levels needed for selected 'mCullResolution'.
|
|
mQuadTreeLevels = (U32)(mCeil(mLog( MaxDimension / mFieldData.mCullResolution ) / mLog( 2.0f )));
|
|
|
|
// Calculate the number of potential nodes required.
|
|
mPotentialFoliageNodes = 0;
|
|
for (U32 n = 0; n <= mQuadTreeLevels; n++)
|
|
mPotentialFoliageNodes += (U32)(mCeil(mPow(4.0f, (F32) n))); // Ceil to be safe!
|
|
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
// Step 2.
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
|
|
// Set Seed.
|
|
RandomGen.setSeed(mFieldData.mSeed);
|
|
|
|
// Add Foliage.
|
|
for (U32 idx = 0; idx < mFieldData.mFoliageCount; idx++)
|
|
{
|
|
fxFoliageItem* pFoliageItem;
|
|
Point3F FoliageOffsetPos;
|
|
|
|
// Reset Relocation Retry.
|
|
RelocationRetry = mFieldData.mFoliageRetries;
|
|
|
|
// Find it a home ...
|
|
do
|
|
{
|
|
// Get the fxFoliageReplicator Position.
|
|
FoliagePosition = getPosition();
|
|
|
|
// Calculate a random offset
|
|
HypX = RandomGen.randF((F32) mFieldData.mInnerRadiusX < mFieldData.mOuterRadiusX ? mFieldData.mInnerRadiusX : mFieldData.mOuterRadiusX, (F32) mFieldData.mOuterRadiusX);
|
|
HypY = RandomGen.randF((F32) mFieldData.mInnerRadiusY < mFieldData.mOuterRadiusY ? mFieldData.mInnerRadiusY : mFieldData.mOuterRadiusY, (F32) mFieldData.mOuterRadiusY);
|
|
Angle = RandomGen.randF(0, (F32) M_2PI);
|
|
|
|
// Calcualte the new position.
|
|
FoliagePosition.x += HypX * mCos(Angle);
|
|
FoliagePosition.y += HypY * mSin(Angle);
|
|
|
|
// Initialise RayCast Search Start/End Positions.
|
|
FoliageStart = FoliageEnd = FoliagePosition;
|
|
FoliageStart.z = 2000.f;
|
|
FoliageEnd.z= -2000.f;
|
|
|
|
// Perform Ray Cast Collision on Client.
|
|
CollisionResult = gClientContainer.castRay( FoliageStart, FoliageEnd, FXFOLIAGEREPLICATOR_COLLISION_MASK, &RayEvent);
|
|
|
|
// Did we hit anything?
|
|
if (CollisionResult)
|
|
{
|
|
// For now, let's pretend we didn't get a collision.
|
|
CollisionResult = false;
|
|
|
|
// Yes, so get it's type.
|
|
U32 CollisionType = RayEvent.object->getTypeMask();
|
|
|
|
// Check Illegal Placements, fail if we hit a disallowed type.
|
|
if (((CollisionType & TerrainObjectType) && !mFieldData.mAllowOnTerrain) ||
|
|
((CollisionType & StaticShapeObjectType ) && !mFieldData.mAllowStatics) ||
|
|
((CollisionType & WaterObjectType) && !mFieldData.mAllowOnWater) ) continue;
|
|
|
|
// If we collided with water and are not allowing on the water surface then let's find the
|
|
// terrain underneath and pass this on as the original collision else fail.
|
|
if ((CollisionType & WaterObjectType) && !mFieldData.mAllowWaterSurface &&
|
|
!gClientContainer.castRay( FoliageStart, FoliageEnd, FXFOLIAGEREPLICATOR_NOWATER_COLLISION_MASK, &RayEvent)) continue;
|
|
|
|
// We passed with flying colour so carry on.
|
|
CollisionResult = true;
|
|
}
|
|
|
|
// Invalidate if we are below Allowed Terrain Angle.
|
|
if (RayEvent.normal.z < mSin(mDegToRad(90.0f-mFieldData.mAllowedTerrainSlope))) CollisionResult = false;
|
|
|
|
// Wait until we get a collision.
|
|
} while(!CollisionResult && --RelocationRetry);
|
|
|
|
// Check for Relocation Problem.
|
|
if (RelocationRetry > 0)
|
|
{
|
|
// Adjust Impact point.
|
|
RayEvent.point.z += mFieldData.mOffsetZ;
|
|
|
|
// Set New Position.
|
|
FoliagePosition = RayEvent.point;
|
|
}
|
|
else
|
|
{
|
|
// Warning.
|
|
Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Could not find satisfactory position for Foliage!");
|
|
|
|
// Skip to next.
|
|
continue;
|
|
}
|
|
|
|
// Monitor the total volume.
|
|
FoliageOffsetPos = FoliagePosition - getPosition();
|
|
MinPoint.setMin(FoliageOffsetPos);
|
|
MaxPoint.setMax(FoliageOffsetPos);
|
|
|
|
// Create our Foliage Item.
|
|
pFoliageItem = new fxFoliageItem;
|
|
|
|
// Reset Frame Serial.
|
|
pFoliageItem->LastFrameSerialID = 0;
|
|
|
|
// Reset Transform.
|
|
pFoliageItem->Transform.identity();
|
|
|
|
// Set Position.
|
|
pFoliageItem->Transform.setColumn(3, FoliagePosition);
|
|
|
|
// Are we fixing size @ max?
|
|
if (mFieldData.mFixSizeToMax)
|
|
{
|
|
// Yes, so set height maximum height.
|
|
pFoliageItem->Height = mFieldData.mMaxHeight;
|
|
// Is the Aspect Ratio Fixed?
|
|
if (mFieldData.mFixAspectRatio)
|
|
// Yes, so lock to height.
|
|
pFoliageItem->Width = pFoliageItem->Height;
|
|
else
|
|
// No, so set width to maximum width.
|
|
pFoliageItem->Width = mFieldData.mMaxWidth;
|
|
}
|
|
else
|
|
{
|
|
// No, so choose a new Scale.
|
|
pFoliageItem->Height = RandomGen.randF(mFieldData.mMinHeight, mFieldData.mMaxHeight);
|
|
// Is the Aspect Ratio Fixed?
|
|
if (mFieldData.mFixAspectRatio)
|
|
// Yes, so lock to height.
|
|
pFoliageItem->Width = pFoliageItem->Height;
|
|
else
|
|
// No, so choose a random width.
|
|
pFoliageItem->Width = RandomGen.randF(mFieldData.mMinWidth, mFieldData.mMaxWidth);
|
|
}
|
|
|
|
// Are we randomly flipping horizontally?
|
|
if (mFieldData.mRandomFlip)
|
|
// Yes, so choose a random flip for this object.
|
|
pFoliageItem->Flipped = (RandomGen.randF(0, 1000) < 500.0f) ? false : true;
|
|
else
|
|
// No, so turn-off flipping.
|
|
pFoliageItem->Flipped = false;
|
|
|
|
// Calculate Foliage Item World Box.
|
|
// NOTE:- We generate a psuedo-volume here. It's basically the volume to which the
|
|
// plane can move and this includes swaying!
|
|
//
|
|
// Is Sway On?
|
|
if (mFieldData.mSwayOn)
|
|
{
|
|
// Yes, so take swaying into account...
|
|
pFoliageItem->FoliageBox.minExtents = FoliagePosition +
|
|
Point3F(-pFoliageItem->Width / 2.0f - mFieldData.mSwayMagnitudeSide,
|
|
-0.5f - mFieldData.mSwayMagnitudeFront,
|
|
pFoliageItem->Height );
|
|
|
|
pFoliageItem->FoliageBox.maxExtents = FoliagePosition +
|
|
Point3F(+pFoliageItem->Width / 2.0f + mFieldData.mSwayMagnitudeSide,
|
|
+0.5f + mFieldData.mSwayMagnitudeFront,
|
|
pFoliageItem->Height );
|
|
}
|
|
else
|
|
{
|
|
// No, so give it a minimum volume...
|
|
pFoliageItem->FoliageBox.minExtents = FoliagePosition +
|
|
Point3F(-pFoliageItem->Width / 2.0f,
|
|
-0.5f,
|
|
pFoliageItem->Height );
|
|
|
|
pFoliageItem->FoliageBox.maxExtents = FoliagePosition +
|
|
Point3F(+pFoliageItem->Width / 2.0f,
|
|
+0.5f,
|
|
pFoliageItem->Height );
|
|
}
|
|
|
|
// Store Shape in Replicated Shapes Vector.
|
|
mReplicatedFoliage.push_back(pFoliageItem);
|
|
|
|
// Increase Foliage Count.
|
|
mCurrentFoliageCount++;
|
|
}
|
|
|
|
// Is Lighting On?
|
|
if (mFieldData.mLightOn)
|
|
{
|
|
// Yes, so reset Global Light phase.
|
|
mGlobalLightPhase = 0.0f;
|
|
// Set Global Light Time Ratio.
|
|
mGlobalLightTimeRatio = PeriodLenMinus / mFieldData.mLightTime;
|
|
|
|
// Yes, so step through Foliage.
|
|
for (U32 idx = 0; idx < mCurrentFoliageCount; idx++)
|
|
{
|
|
fxFoliageItem* pFoliageItem;
|
|
|
|
// Fetch the Foliage Item.
|
|
pFoliageItem = mReplicatedFoliage[idx];
|
|
|
|
// Do we have an item?
|
|
if (pFoliageItem)
|
|
{
|
|
// Yes, so are lights syncronised?
|
|
if (mFieldData.mLightSync)
|
|
{
|
|
pFoliageItem->LightTimeRatio = 1.0f;
|
|
pFoliageItem->LightPhase = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
// No, so choose a random Light phase.
|
|
pFoliageItem->LightPhase = RandomGen.randF(0, PeriodLenMinus);
|
|
// Set Light Time Ratio.
|
|
pFoliageItem->LightTimeRatio = PeriodLenMinus / mFieldData.mLightTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Is Swaying Enabled?
|
|
if (mFieldData.mSwayOn)
|
|
{
|
|
// Yes, so reset Global Sway phase.
|
|
mGlobalSwayPhase = 0.0f;
|
|
// Always set Global Sway Time Ratio.
|
|
mGlobalSwayTimeRatio = PeriodLenMinus / RandomGen.randF(mFieldData.mMinSwayTime, mFieldData.mMaxSwayTime);
|
|
|
|
// Yes, so step through Foliage.
|
|
for (U32 idx = 0; idx < mCurrentFoliageCount; idx++)
|
|
{
|
|
fxFoliageItem* pFoliageItem;
|
|
|
|
// Fetch the Foliage Item.
|
|
pFoliageItem = mReplicatedFoliage[idx];
|
|
// Do we have an item?
|
|
if (pFoliageItem)
|
|
{
|
|
// Are we using Sway Sync?
|
|
if (mFieldData.mSwaySync)
|
|
{
|
|
pFoliageItem->SwayPhase = 0;
|
|
pFoliageItem->SwayTimeRatio = mGlobalSwayTimeRatio;
|
|
}
|
|
else
|
|
{
|
|
// No, so choose a random Sway phase.
|
|
pFoliageItem->SwayPhase = RandomGen.randF(0, PeriodLenMinus);
|
|
// Set to random Sway Time.
|
|
pFoliageItem->SwayTimeRatio = PeriodLenMinus / RandomGen.randF(mFieldData.mMinSwayTime, mFieldData.mMaxSwayTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update our Object Volume.
|
|
mObjBox.minExtents.set(MinPoint);
|
|
mObjBox.maxExtents.set(MaxPoint);
|
|
setTransform(mObjToWorld);
|
|
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
// Step 3.
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
|
|
// Reset Next Allocated Node to Stack base.
|
|
mNextAllocatedNodeIdx = 0;
|
|
|
|
// Allocate a new Node.
|
|
fxFoliageQuadrantNode* pNewNode = new fxFoliageQuadrantNode;
|
|
|
|
// Store it in the Quad-tree.
|
|
mFoliageQuadTree.push_back(pNewNode);
|
|
|
|
// Populate Initial Node.
|
|
//
|
|
// Set Start Level.
|
|
pNewNode->Level = mQuadTreeLevels;
|
|
// Calculate Total Foliage Area.
|
|
pNewNode->QuadrantBox = getWorldBox();
|
|
// Reset Quadrant child nodes.
|
|
pNewNode->QuadrantChildNode[0] =
|
|
pNewNode->QuadrantChildNode[1] =
|
|
pNewNode->QuadrantChildNode[2] =
|
|
pNewNode->QuadrantChildNode[3] = NULL;
|
|
|
|
// Create our initial cull list with *all* billboards into.
|
|
fxFoliageCulledList CullList;
|
|
CullList.mCulledObjectSet = mReplicatedFoliage;
|
|
|
|
// Move to next node Index.
|
|
mNextAllocatedNodeIdx++;
|
|
|
|
// Let's start this thing going by recursing it's children.
|
|
ProcessNodeChildren(pNewNode, &CullList);
|
|
|
|
// Calculate Elapsed Time and take new Timestamp.
|
|
F32 ElapsedTime = (Platform::getRealMilliseconds() - mStartCreationTime) * 0.001f;
|
|
|
|
// Console Output.
|
|
Con::printf("fxFoliageReplicator - Lev: %d PotNodes: %d Used: %d Objs: %d Time: %0.4fs.",
|
|
mQuadTreeLevels,
|
|
mPotentialFoliageNodes,
|
|
mNextAllocatedNodeIdx-1,
|
|
mBillboardsAcquired,
|
|
ElapsedTime);
|
|
|
|
// Dump (*very*) approximate allocated memory.
|
|
F32 MemoryAllocated = (F32) ((mNextAllocatedNodeIdx-1) * sizeof(fxFoliageQuadrantNode));
|
|
MemoryAllocated += mCurrentFoliageCount * sizeof(fxFoliageItem);
|
|
MemoryAllocated += mCurrentFoliageCount * sizeof(fxFoliageItem*);
|
|
Con::printf("fxFoliageReplicator - Approx. %0.2fMb allocated.", MemoryAllocated / 1048576.0f);
|
|
|
|
// ----------------------------------------------------------------------------------------------------------------------
|
|
|
|
SetupBuffers();
|
|
|
|
// Take first Timestamp.
|
|
mLastRenderTime = Platform::getVirtualMilliseconds();
|
|
}
|
|
|
|
void fxFoliageReplicator::SetupShader()
|
|
{
|
|
if ( !mShaderData )
|
|
{
|
|
if ( !Sim::findObject( "fxFoliageReplicatorShader", mShaderData ) )
|
|
{
|
|
Con::errorf( "fxFoliageReplicator::SetupShader - could not find ShaderData named fxFoliageReplicatorShader" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
Vector<GFXShaderMacro> macros;
|
|
if ( mFieldData.mUseTrueBillboards )
|
|
macros.push_back( GFXShaderMacro( "TRUE_BILLBOARD" ) );
|
|
|
|
mShader = mShaderData->getShader( macros );
|
|
|
|
if ( !mShader )
|
|
return;
|
|
|
|
|
|
mFoliageShaderConsts = mShader->allocConstBuffer();
|
|
|
|
mFoliageShaderProjectionSC = mShader->getShaderConstHandle( "$projection" );
|
|
mFoliageShaderWorldSC = mShader->getShaderConstHandle( "$world" );
|
|
mFoliageShaderGlobalSwayPhaseSC = mShader->getShaderConstHandle( "$GlobalSwayPhase" );
|
|
mFoliageShaderSwayMagnitudeSideSC = mShader->getShaderConstHandle( "$SwayMagnitudeSide" );
|
|
mFoliageShaderSwayMagnitudeFrontSC = mShader->getShaderConstHandle( "$SwayMagnitudeFront" );
|
|
mFoliageShaderGlobalLightPhaseSC = mShader->getShaderConstHandle( "$GlobalLightPhase" );
|
|
mFoliageShaderLuminanceMagnitudeSC = mShader->getShaderConstHandle( "$LuminanceMagnitude" );
|
|
mFoliageShaderLuminanceMidpointSC = mShader->getShaderConstHandle( "$LuminanceMidpoint" );
|
|
mFoliageShaderDistanceRangeSC = mShader->getShaderConstHandle( "$DistanceRange" );
|
|
mFoliageShaderCameraPosSC = mShader->getShaderConstHandle( "$CameraPos" );
|
|
mFoliageShaderTrueBillboardSC = mShader->getShaderConstHandle( "$TrueBillboard" );
|
|
mFoliageShaderGroundAlphaSC = mShader->getShaderConstHandle( "$groundAlpha" );
|
|
mFoliageShaderAmbientColorSC = mShader->getShaderConstHandle( "$ambient" );
|
|
mDiffuseTextureSC = mShader->getShaderConstHandle( "$diffuseMap" );
|
|
mAlphaMapTextureSC = mShader->getShaderConstHandle( "$alphaMap" );
|
|
}
|
|
|
|
// Ok, what we do is let the older code setup the FoliageItem list and the QuadTree.
|
|
// Then we build the Vertex and Primitive buffers here. It would probably be
|
|
// slightly more memory efficient to build the buffers directly, but we
|
|
// want to sort the items within the buffer by the quadtreenodes
|
|
void fxFoliageReplicator::SetupBuffers()
|
|
{
|
|
// Following two arrays are used to build the vertex and primitive buffers.
|
|
Point3F basePoints[8];
|
|
basePoints[0] = Point3F(-0.5f, 0.0f, 1.0f);
|
|
basePoints[1] = Point3F(-0.5f, 0.0f, 0.0f);
|
|
basePoints[2] = Point3F(0.5f, 0.0f, 0.0f);
|
|
basePoints[3] = Point3F(0.5f, 0.0f, 1.0f);
|
|
|
|
Point2F texCoords[4];
|
|
texCoords[0] = Point2F(0.0, 0.0);
|
|
texCoords[1] = Point2F(0.0, 1.0);
|
|
texCoords[2] = Point2F(1.0, 1.0);
|
|
texCoords[3] = Point2F(1.0, 0.0);
|
|
|
|
// Init our Primitive Buffer
|
|
U32 indexSize = mFieldData.mFoliageCount * 6;
|
|
U16* indices = new U16[indexSize];
|
|
// Two triangles per particle
|
|
for (U16 i = 0; i < mFieldData.mFoliageCount; i++) {
|
|
U16* idx = &indices[i*6]; // hey, no offset math below, neat
|
|
U16 vertOffset = i*4;
|
|
idx[0] = vertOffset + 0;
|
|
idx[1] = vertOffset + 1;
|
|
idx[2] = vertOffset + 2;
|
|
idx[3] = vertOffset + 2;
|
|
idx[4] = vertOffset + 3;
|
|
idx[5] = vertOffset + 0;
|
|
}
|
|
// Init the prim buffer and copy our indexes over
|
|
U16 *ibIndices;
|
|
mPrimBuffer.set(GFX, indexSize, 0, GFXBufferTypeStatic);
|
|
mPrimBuffer.lock(&ibIndices);
|
|
dMemcpy(ibIndices, indices, indexSize * sizeof(U16));
|
|
mPrimBuffer.unlock();
|
|
delete[] indices;
|
|
|
|
// Now, let's init the vertex buffer
|
|
U32 currPrimitiveStartIndex = 0;
|
|
mVertexBuffer.set(GFX, mFieldData.mFoliageCount * 4, GFXBufferTypeStatic);
|
|
mVertexBuffer.lock();
|
|
U32 idx = 0;
|
|
for (S32 qtIdx = 0; qtIdx < mFoliageQuadTree.size(); qtIdx++) {
|
|
fxFoliageQuadrantNode* quadNode = mFoliageQuadTree[qtIdx];
|
|
if (quadNode->Level == 0) {
|
|
quadNode->startIndex = currPrimitiveStartIndex;
|
|
quadNode->primitiveCount = 0;
|
|
// Ok, there should be data in here!
|
|
for (S32 i = 0; i < quadNode->RenderList.size(); i++) {
|
|
fxFoliageItem* pFoliageItem = quadNode->RenderList[i];
|
|
if (pFoliageItem->LastFrameSerialID == 0) {
|
|
pFoliageItem->LastFrameSerialID++;
|
|
// Dump it into the vertex buffer
|
|
for (U32 vertIndex = 0; vertIndex < 4; vertIndex++) {
|
|
GFXVertexFoliage *vert = &mVertexBuffer[(idx*4) + vertIndex];
|
|
// This is the position of the billboard.
|
|
vert->point = pFoliageItem->Transform.getPosition();
|
|
// Normal contains the point of the billboard (except for the y component, see below)
|
|
vert->normal = basePoints[vertIndex];
|
|
|
|
vert->normal.x *= pFoliageItem->Width;
|
|
vert->normal.z *= pFoliageItem->Height;
|
|
// Handle texture coordinates
|
|
vert->texCoord = texCoords[vertIndex];
|
|
if (pFoliageItem->Flipped)
|
|
vert->texCoord.x = 1.0f - vert->texCoord.x;
|
|
// Handle sway. Sway is stored in a texture coord. The x coordinate is the sway phase multiplier,
|
|
// the y coordinate determines if this vertex actually sways or not.
|
|
if ((vertIndex == 0) || (vertIndex == 3)) {
|
|
vert->texCoord2.set(pFoliageItem->SwayTimeRatio / mGlobalSwayTimeRatio, 1.0f);
|
|
} else {
|
|
vert->texCoord2.set(0.0f, 0.0f);
|
|
}
|
|
// Handle lighting, lighting happens at the same time as global so this is just an offset.
|
|
vert->normal.y = pFoliageItem->LightPhase;
|
|
}
|
|
idx++;
|
|
quadNode->primitiveCount += 2;
|
|
currPrimitiveStartIndex += 6;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mVertexBuffer.unlock();
|
|
|
|
DestroyFoliageItems();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
Box3F fxFoliageReplicator::FetchQuadrant(const Box3F& Box, U32 Quadrant)
|
|
{
|
|
Box3F QuadrantBox;
|
|
|
|
// Select Quadrant.
|
|
switch(Quadrant)
|
|
{
|
|
// UL.
|
|
case 0:
|
|
QuadrantBox.minExtents = Box.minExtents + Point3F(0, Box.len_y()/2, 0);
|
|
QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z());
|
|
break;
|
|
|
|
// UR.
|
|
case 1:
|
|
QuadrantBox.minExtents = Box.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, 0);
|
|
QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z());
|
|
break;
|
|
|
|
// LL.
|
|
case 2:
|
|
QuadrantBox.minExtents = Box.minExtents;
|
|
QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z());
|
|
break;
|
|
|
|
// LR.
|
|
case 3:
|
|
QuadrantBox.minExtents = Box.minExtents + Point3F(Box.len_x()/2, 0, 0);
|
|
QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z());
|
|
break;
|
|
|
|
default:
|
|
return Box;
|
|
}
|
|
|
|
return QuadrantBox;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::ProcessNodeChildren(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList)
|
|
{
|
|
// ---------------------------------------------------------------
|
|
// Split Node into Quadrants and Process each.
|
|
// ---------------------------------------------------------------
|
|
|
|
// Process All Quadrants (UL/UR/LL/LR).
|
|
for (U32 q = 0; q < 4; q++)
|
|
ProcessQuadrant(pParentNode, pCullList, q);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::ProcessQuadrant(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList, U32 Quadrant)
|
|
{
|
|
// Fetch Quadrant Box.
|
|
const Box3F QuadrantBox = FetchQuadrant(pParentNode->QuadrantBox, Quadrant);
|
|
|
|
// Create our new Cull List.
|
|
fxFoliageCulledList CullList(QuadrantBox, pCullList);
|
|
|
|
// Did we get any objects?
|
|
if (CullList.GetListCount() > 0)
|
|
{
|
|
// Yes, so allocate a new Node.
|
|
fxFoliageQuadrantNode* pNewNode = new fxFoliageQuadrantNode;
|
|
|
|
// Store it in the Quad-tree.
|
|
mFoliageQuadTree.push_back(pNewNode);
|
|
|
|
// Move to next node Index.
|
|
mNextAllocatedNodeIdx++;
|
|
|
|
// Populate Quadrant Node.
|
|
//
|
|
// Next Sub-level.
|
|
pNewNode->Level = pParentNode->Level - 1;
|
|
// Calculate Quadrant Box.
|
|
pNewNode->QuadrantBox = QuadrantBox;
|
|
// Reset Child Nodes.
|
|
pNewNode->QuadrantChildNode[0] =
|
|
pNewNode->QuadrantChildNode[1] =
|
|
pNewNode->QuadrantChildNode[2] =
|
|
pNewNode->QuadrantChildNode[3] = NULL;
|
|
|
|
// Put a reference in parent.
|
|
pParentNode->QuadrantChildNode[Quadrant] = pNewNode;
|
|
|
|
// If we're not at sub-level 0 then process this nodes children.
|
|
if (pNewNode->Level != 0) ProcessNodeChildren(pNewNode, &CullList);
|
|
// If we've reached sub-level 0 then store Cull List (for rendering).
|
|
if (pNewNode->Level == 0)
|
|
{
|
|
// Store the render list from our culled object set.
|
|
pNewNode->RenderList = CullList.mCulledObjectSet;
|
|
// Keep track of the total billboard acquired.
|
|
mBillboardsAcquired += CullList.GetListCount();
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::SyncFoliageReplicators(void)
|
|
{
|
|
// Check Host.
|
|
AssertFatal(isServerObject(), "We *MUST* be on server when Synchronising Foliage!");
|
|
|
|
// Find the Replicator Set.
|
|
SimSet *fxFoliageSet = dynamic_cast<SimSet*>(Sim::findObject("fxFoliageSet"));
|
|
|
|
// Return if Error.
|
|
if (!fxFoliageSet)
|
|
{
|
|
// Console Warning.
|
|
Con::warnf("fxFoliageReplicator - Cannot locate the 'fxFoliageSet', this is bad!");
|
|
// Return here.
|
|
return;
|
|
}
|
|
|
|
// Parse Replication Object(s).
|
|
for (SimSetIterator itr(fxFoliageSet); *itr; ++itr)
|
|
{
|
|
// Fetch the Replicator Object.
|
|
fxFoliageReplicator* Replicator = static_cast<fxFoliageReplicator*>(*itr);
|
|
// Set Foliage Replication Mask.
|
|
if (Replicator->isServerObject())
|
|
{
|
|
Con::printf("fxFoliageReplicator - Restarting fxFoliageReplicator Object...");
|
|
Replicator->setMaskBits(FoliageReplicationMask);
|
|
}
|
|
}
|
|
|
|
// Info ...
|
|
Con::printf("fxFoliageReplicator - Client Foliage Sync has completed.");
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Lets chill our memory requirements out a little
|
|
void fxFoliageReplicator::DestroyFoliageItems()
|
|
{
|
|
// Remove shapes.
|
|
for (S32 idx = 0; idx < mReplicatedFoliage.size(); idx++)
|
|
{
|
|
fxFoliageItem* pFoliageItem;
|
|
|
|
// Fetch the Foliage Item.
|
|
pFoliageItem = mReplicatedFoliage[idx];
|
|
|
|
// Delete Shape.
|
|
if (pFoliageItem) delete pFoliageItem;
|
|
}
|
|
// Clear the Replicated Foliage Vector.
|
|
mReplicatedFoliage.clear();
|
|
|
|
// Clear out old references also
|
|
for (S32 qtIdx = 0; qtIdx < mFoliageQuadTree.size(); qtIdx++) {
|
|
fxFoliageQuadrantNode* quadNode = mFoliageQuadTree[qtIdx];
|
|
if (quadNode->Level == 0) {
|
|
quadNode->RenderList.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void fxFoliageReplicator::DestroyFoliage(void)
|
|
{
|
|
// Check Host.
|
|
AssertFatal(isClientObject(), "Trying to destroy Foliage on Server, this is bad!");
|
|
|
|
// Destroy Quad-tree.
|
|
mPotentialFoliageNodes = 0;
|
|
// Reset Billboards Acquired.
|
|
mBillboardsAcquired = 0;
|
|
|
|
// Finish if we didn't create any shapes.
|
|
if (mCurrentFoliageCount == 0) return;
|
|
|
|
DestroyFoliageItems();
|
|
|
|
// Let's remove the Quad-Tree allocations.
|
|
for ( Vector<fxFoliageQuadrantNode*>::iterator QuadNodeItr = mFoliageQuadTree.begin();
|
|
QuadNodeItr != mFoliageQuadTree.end();
|
|
QuadNodeItr++ )
|
|
{
|
|
// Remove the node.
|
|
delete *QuadNodeItr;
|
|
}
|
|
|
|
// Clear the Foliage Quad-Tree Vector.
|
|
mFoliageQuadTree.clear();
|
|
|
|
// Clear the Frustum Render Set Vector.
|
|
mFrustumRenderSet.mVisObjectSet.clear();
|
|
|
|
// Reset Foliage Count.
|
|
mCurrentFoliageCount = 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::StartUp(void)
|
|
{
|
|
// Flag, Client Replication Started.
|
|
mClientReplicationStarted = true;
|
|
|
|
// Create foliage on Client.
|
|
if (isClientObject()) CreateFoliage();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool fxFoliageReplicator::onAdd()
|
|
{
|
|
if(!Parent::onAdd()) return(false);
|
|
|
|
// Add the Replicator to the Replicator Set.
|
|
dynamic_cast<SimSet*>(Sim::findObject("fxFoliageSet"))->addObject(this);
|
|
|
|
// Set Default Object Box.
|
|
mObjBox.minExtents.set( -0.5, -0.5, -0.5 );
|
|
mObjBox.maxExtents.set( 0.5, 0.5, 0.5 );
|
|
resetWorldBox();
|
|
setRenderTransform(mObjToWorld);
|
|
|
|
// Add to Scene.
|
|
addToScene();
|
|
|
|
// Are we on the client?
|
|
if ( isClientObject() )
|
|
{
|
|
// Yes, so load foliage texture.
|
|
if( mFieldData.mFoliageFile != NULL && dStrlen(mFieldData.mFoliageFile) > 0 )
|
|
mFieldData.mFoliageTexture = GFXTexHandle( mFieldData.mFoliageFile, &GFXStaticTextureSRGBProfile, avar("%s() - mFieldData.mFoliageTexture (line %d)", __FUNCTION__, __LINE__) );
|
|
|
|
if ((GFXTextureObject*) mFieldData.mFoliageTexture == NULL)
|
|
Con::printf("fxFoliageReplicator: %s is an invalid or missing foliage texture file.", mFieldData.mFoliageFile);
|
|
|
|
mAlphaLookup = new GBitmap(AlphaTexLen, 1);
|
|
computeAlphaTex();
|
|
|
|
// Register for notification when GhostAlways objects are done loading
|
|
NetConnection::smGhostAlwaysDone.notify( this, &fxFoliageReplicator::onGhostAlwaysDone );
|
|
|
|
SetupShader();
|
|
}
|
|
|
|
// Return OK.
|
|
return(true);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::onRemove()
|
|
{
|
|
// Remove the Replicator from the Replicator Set.
|
|
dynamic_cast<SimSet*>(Sim::findObject("fxFoliageSet"))->removeObject(this);
|
|
|
|
NetConnection::smGhostAlwaysDone.remove( this, &fxFoliageReplicator::onGhostAlwaysDone );
|
|
|
|
// Remove from Scene.
|
|
removeFromScene();
|
|
|
|
// Are we on the Client?
|
|
if (isClientObject())
|
|
{
|
|
// Yes, so destroy Foliage.
|
|
DestroyFoliage();
|
|
|
|
// Remove Texture.
|
|
mFieldData.mFoliageTexture = NULL;
|
|
|
|
mShader = NULL;
|
|
}
|
|
|
|
// Do Parent.
|
|
Parent::onRemove();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::onGhostAlwaysDone()
|
|
{
|
|
if ( isClientObject() )
|
|
CreateFoliage();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::inspectPostApply()
|
|
{
|
|
// Set Parent.
|
|
Parent::inspectPostApply();
|
|
|
|
// Set Foliage Replication Mask (this object only).
|
|
setMaskBits(FoliageReplicationMask);
|
|
|
|
mDirty = true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
DefineEngineFunction(StartFoliageReplication, void,(),, "Activates the foliage replicator.\n"
|
|
"@tsexample\n"
|
|
"// Call the function\n"
|
|
"StartFoliageReplication();\n"
|
|
"@endtsexample\n"
|
|
"@ingroup Foliage")
|
|
{
|
|
// Find the Replicator Set.
|
|
SimSet *fxFoliageSet = dynamic_cast<SimSet*>(Sim::findObject("fxFoliageSet"));
|
|
|
|
// Return if Error.
|
|
if (!fxFoliageSet)
|
|
{
|
|
// Console Warning.
|
|
Con::warnf("fxFoliageReplicator - Cannot locate the 'fxFoliageSet', this is bad!");
|
|
// Return here.
|
|
return;
|
|
}
|
|
|
|
// Parse Replication Object(s).
|
|
U32 startupCount = 0;
|
|
for (SimSetIterator itr(fxFoliageSet); *itr; ++itr)
|
|
{
|
|
// Fetch the Replicator Object.
|
|
fxFoliageReplicator* Replicator = static_cast<fxFoliageReplicator*>(*itr);
|
|
|
|
// Start Client Objects Only.
|
|
if (Replicator->isClientObject())
|
|
{
|
|
Replicator->StartUp();
|
|
startupCount++;
|
|
}
|
|
}
|
|
|
|
// Info ...
|
|
Con::printf("fxFoliageReplicator - replicated client foliage for %d objects", startupCount);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::prepRenderImage( SceneRenderState* state )
|
|
{
|
|
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
|
ri->renderDelegate.bind(this, &fxFoliageReplicator::renderObject);
|
|
ri->type = RenderPassManager::RIT_Foliage;
|
|
state->getRenderPass()->addInst( ri );
|
|
}
|
|
|
|
//
|
|
// RENDERING
|
|
//
|
|
void fxFoliageReplicator::computeAlphaTex()
|
|
{
|
|
// Distances used in alpha
|
|
const F32 ClippedViewDistance = mFieldData.mViewDistance;
|
|
const F32 MaximumViewDistance = ClippedViewDistance + mFieldData.mFadeInRegion;
|
|
|
|
// This is used for the alpha computation in the shader.
|
|
for (U32 i = 0; i < AlphaTexLen; i++) {
|
|
F32 Distance = ((float) i / (float) AlphaTexLen) * MaximumViewDistance;
|
|
F32 ItemAlpha = 1.0f;
|
|
// Are we fading out?
|
|
if (Distance < mFieldData.mViewClosest)
|
|
{
|
|
// Yes, so set fade-out.
|
|
ItemAlpha = 1.0f - ((mFieldData.mViewClosest - Distance) * mFadeOutGradient);
|
|
}
|
|
// No, so are we fading in?
|
|
else if (Distance > ClippedViewDistance)
|
|
{
|
|
// Yes, so set fade-in
|
|
ItemAlpha = 1.0f - ((Distance - ClippedViewDistance) * mFadeInGradient);
|
|
}
|
|
|
|
// Set texture info
|
|
ColorI c((U8) (255.0f * ItemAlpha), 0, 0);
|
|
mAlphaLookup->setColor(i, 0, c);
|
|
}
|
|
mAlphaTexture.set(mAlphaLookup, &GFXStaticTextureSRGBProfile, false, String("fxFoliage Replicator Alpha Texture") );
|
|
}
|
|
|
|
// Renders a triangle stripped oval
|
|
void fxFoliageReplicator::renderArc(const F32 fRadiusX, const F32 fRadiusY)
|
|
{
|
|
PrimBuild::begin(GFXTriangleStrip, 720);
|
|
for (U32 Angle = mCreationAreaAngle; Angle < (mCreationAreaAngle+360); Angle++)
|
|
{
|
|
F32 XPos, YPos;
|
|
|
|
// Calculate Position.
|
|
XPos = fRadiusX * mCos(mDegToRad(-(F32)Angle));
|
|
YPos = fRadiusY * mSin(mDegToRad(-(F32)Angle));
|
|
|
|
// Set Colour.
|
|
PrimBuild::color4f(mFieldData.mPlaceAreaColour.red,
|
|
mFieldData.mPlaceAreaColour.green,
|
|
mFieldData.mPlaceAreaColour.blue,
|
|
AREA_ANIMATION_ARC * (Angle-mCreationAreaAngle));
|
|
|
|
PrimBuild::vertex3f(XPos, YPos, -(F32)mFieldData.mPlacementBandHeight/2.0f);
|
|
PrimBuild::vertex3f(XPos, YPos, +(F32)mFieldData.mPlacementBandHeight/2.0f);
|
|
}
|
|
PrimBuild::end();
|
|
}
|
|
|
|
// This currently uses the primbuilder, could convert out, but why allocate the buffer if we
|
|
// never edit the misison?
|
|
void fxFoliageReplicator::renderPlacementArea(const F32 ElapsedTime)
|
|
{
|
|
if (gEditingMission && mFieldData.mShowPlacementArea)
|
|
{
|
|
GFX->pushWorldMatrix();
|
|
GFX->multWorld(getTransform());
|
|
|
|
if (!mPlacementSB)
|
|
{
|
|
GFXStateBlockDesc transparent;
|
|
transparent.setCullMode(GFXCullNone);
|
|
transparent.alphaTestEnable = true;
|
|
transparent.setZReadWrite(true);
|
|
transparent.zWriteEnable = false;
|
|
transparent.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
|
|
mPlacementSB = GFX->createStateBlock( transparent );
|
|
}
|
|
|
|
GFX->setStateBlock(mPlacementSB);
|
|
|
|
// Do we need to draw the Outer Radius?
|
|
if (mFieldData.mOuterRadiusX || mFieldData.mOuterRadiusY)
|
|
renderArc((F32) mFieldData.mOuterRadiusX, (F32) mFieldData.mOuterRadiusY);
|
|
// Inner radius?
|
|
if (mFieldData.mInnerRadiusX || mFieldData.mInnerRadiusY)
|
|
renderArc((F32) mFieldData.mInnerRadiusX, (F32) mFieldData.mInnerRadiusY);
|
|
|
|
GFX->popWorldMatrix();
|
|
mCreationAreaAngle = (U32)(mCreationAreaAngle + (1000 * ElapsedTime));
|
|
mCreationAreaAngle = mCreationAreaAngle % 360;
|
|
}
|
|
}
|
|
|
|
void fxFoliageReplicator::renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat)
|
|
{
|
|
if (overrideMat)
|
|
return;
|
|
|
|
if ( !mShader )
|
|
return;
|
|
|
|
// If we're rendering and we haven't placed any foliage yet - do it.
|
|
if(!mClientReplicationStarted)
|
|
{
|
|
Con::warnf("fxFoliageReplicator::renderObject - tried to render a non replicated fxFoliageReplicator; replicating it now...");
|
|
|
|
StartUp();
|
|
}
|
|
|
|
// Calculate Elapsed Time and take new Timestamp.
|
|
S32 Time = Platform::getVirtualMilliseconds();
|
|
F32 ElapsedTime = (Time - mLastRenderTime) * 0.001f;
|
|
mLastRenderTime = Time;
|
|
|
|
renderPlacementArea(ElapsedTime);
|
|
|
|
if (mCurrentFoliageCount > 0) {
|
|
|
|
if ( mRenderSB.isNull() || mDirty)
|
|
{
|
|
mDirty = false;
|
|
|
|
GFXStateBlockDesc desc;
|
|
|
|
// Debug SB
|
|
desc.samplersDefined = true;
|
|
desc.samplers[0].textureColorOp = GFXTOPDisable;
|
|
desc.samplers[1].textureColorOp = GFXTOPDisable;
|
|
|
|
mDebugSB = GFX->createStateBlock(desc);
|
|
|
|
// Render SB
|
|
desc.samplers[0].textureColorOp = GFXTOPModulate;
|
|
desc.samplers[1].textureColorOp = GFXTOPModulate;
|
|
desc.samplers[1].addressModeU = GFXAddressClamp;
|
|
desc.samplers[1].addressModeV = GFXAddressClamp;
|
|
|
|
desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
|
|
desc.setAlphaTest(true, GFXCmpGreater, (U8) (255.0f * mFieldData.mAlphaCutoff));
|
|
desc.setCullMode(GFXCullNone);
|
|
|
|
mRenderSB = GFX->createStateBlock(desc);
|
|
}
|
|
|
|
if (!mFieldData.mHideFoliage) {
|
|
// Animate Global Sway Phase (Modulus).
|
|
mGlobalSwayPhase = mGlobalSwayPhase + (mGlobalSwayTimeRatio * ElapsedTime);
|
|
|
|
// Animate Global Light Phase (Modulus).
|
|
mGlobalLightPhase = mGlobalLightPhase + (mGlobalLightTimeRatio * ElapsedTime);
|
|
|
|
// Compute other light parameters
|
|
const F32 LuminanceMidPoint = (mFieldData.mMinLuminance + mFieldData.mMaxLuminance) / 2.0f;
|
|
const F32 LuminanceMagnitude = mFieldData.mMaxLuminance - LuminanceMidPoint;
|
|
|
|
// Distances used in alpha
|
|
const F32 ClippedViewDistance = mFieldData.mViewDistance;
|
|
const F32 MaximumViewDistance = ClippedViewDistance + mFieldData.mFadeInRegion;
|
|
|
|
if (mFoliageShaderConsts.isValid())
|
|
{
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderGlobalSwayPhaseSC, mGlobalSwayPhase);
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderSwayMagnitudeSideSC, mFieldData.mSwayMagnitudeSide);
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderSwayMagnitudeFrontSC, mFieldData.mSwayMagnitudeFront);
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderGlobalLightPhaseSC, mGlobalLightPhase);
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderLuminanceMagnitudeSC, LuminanceMagnitude);
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderLuminanceMidpointSC, LuminanceMidPoint);
|
|
|
|
// Set up our shader constants
|
|
// Projection matrix
|
|
MatrixF proj = GFX->getProjectionMatrix();
|
|
//proj.transpose();
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderProjectionSC, proj);
|
|
|
|
// World transform matrix
|
|
MatrixF world = GFX->getWorldMatrix();
|
|
//world.transpose();
|
|
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderWorldSC, world);
|
|
|
|
Point3F camPos = state->getCameraPosition();
|
|
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderDistanceRangeSC, MaximumViewDistance);
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderCameraPosSC, camPos);
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderTrueBillboardSC, mFieldData.mUseTrueBillboards ? 1.0f : 0.0f );
|
|
mFoliageShaderConsts->setSafe(mFoliageShaderGroundAlphaSC, Point4F(mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha));
|
|
|
|
if (mFoliageShaderAmbientColorSC->isValid())
|
|
{
|
|
LinearColorF ambient = state->getAmbientLightColor();
|
|
LinearColorF ambient_inv(1.0f-ambient.red, 1.0f-ambient.green, 1.0f-ambient.blue, 0.0f);
|
|
ambient += ambient_inv*(1.0f - mFieldData.mAmbientModulationBias);
|
|
mFoliageShaderConsts->set(mFoliageShaderAmbientColorSC, ambient);
|
|
}
|
|
|
|
GFX->setShaderConstBuffer(mFoliageShaderConsts);
|
|
|
|
}
|
|
|
|
// Blend ops
|
|
// Set up our texture and color ops.
|
|
|
|
GFX->setStateBlock(mRenderSB);
|
|
GFX->setShader( mShader );
|
|
|
|
GFX->setTexture(mDiffuseTextureSC->getSamplerRegister(), mFieldData.mFoliageTexture);
|
|
// computeAlphaTex(); // Uncomment if we figure out how to clamp to fogAndHaze
|
|
GFX->setTexture(mAlphaMapTextureSC->getSamplerRegister(), mAlphaTexture);
|
|
|
|
// Setup our buffers
|
|
GFX->setVertexBuffer(mVertexBuffer);
|
|
GFX->setPrimitiveBuffer(mPrimBuffer);
|
|
|
|
// If we use culling, we're going to send chunks of our buffers to the card
|
|
if (mFieldData.mUseCulling)
|
|
{
|
|
// Setup the Clip-Planes.
|
|
F32 FarClipPlane = getMin((F32)state->getFarPlane(),
|
|
mFieldData.mViewDistance + mFieldData.mFadeInRegion);
|
|
mFrustumRenderSet.SetupClipPlanes(state, FarClipPlane);
|
|
|
|
renderQuad(mFoliageQuadTree[0], getRenderTransform(), false);
|
|
|
|
// Multipass, don't want to interrupt the vb state
|
|
if (mFieldData.mUseDebugInfo)
|
|
{
|
|
// hey man, we're done, so it doesn't matter if we kill it to render the next part
|
|
GFX->setStateBlock(mDebugSB);
|
|
renderQuad(mFoliageQuadTree[0], getRenderTransform(), true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Draw the whole shebang!
|
|
GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, mVertexBuffer->mNumVerts,
|
|
0, mPrimBuffer->mIndexCount / 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void fxFoliageReplicator::renderQuad(fxFoliageQuadrantNode* quadNode, const MatrixF& RenderTransform, const bool UseDebug)
|
|
{
|
|
if (quadNode != NULL) {
|
|
if (mFrustumRenderSet.IsQuadrantVisible(quadNode->QuadrantBox, RenderTransform))
|
|
{
|
|
// Draw the Quad Box (Debug Only).
|
|
if (UseDebug)
|
|
mFrustumRenderSet.DrawQuadBox(quadNode->QuadrantBox, LinearColorF(0.0f, 1.0f, 0.1f, 1.0f));
|
|
if (quadNode->Level != 0) {
|
|
for (U32 i = 0; i < 4; i++)
|
|
renderQuad(quadNode->QuadrantChildNode[i], RenderTransform, UseDebug);
|
|
} else {
|
|
if (!UseDebug)
|
|
if(quadNode->primitiveCount)
|
|
GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, mVertexBuffer->mNumVerts,
|
|
quadNode->startIndex, quadNode->primitiveCount);
|
|
}
|
|
} else {
|
|
// Use a different color to say "I think I'm not visible!"
|
|
if (UseDebug)
|
|
mFrustumRenderSet.DrawQuadBox(quadNode->QuadrantBox, LinearColorF(1.0f, 0.8f, 0.1f, 1.0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// NETWORK
|
|
//------------------------------------------------------------------------------
|
|
|
|
U32 fxFoliageReplicator::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
|
|
{
|
|
// Pack Parent.
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
// Write Foliage Replication Flag.
|
|
if (stream->writeFlag(mask & FoliageReplicationMask))
|
|
{
|
|
stream->writeAffineTransform(mObjToWorld); // Foliage Master-Object Position.
|
|
|
|
stream->writeFlag(mFieldData.mUseDebugInfo); // Foliage Debug Information Flag.
|
|
stream->write(mFieldData.mDebugBoxHeight); // Foliage Debug Height.
|
|
stream->write(mFieldData.mSeed); // Foliage Seed.
|
|
stream->write(mFieldData.mFoliageCount); // Foliage Count.
|
|
stream->write(mFieldData.mFoliageRetries); // Foliage Retries.
|
|
stream->writeString(mFieldData.mFoliageFile); // Foliage File.
|
|
|
|
stream->write(mFieldData.mInnerRadiusX); // Foliage Inner Radius X.
|
|
stream->write(mFieldData.mInnerRadiusY); // Foliage Inner Radius Y.
|
|
stream->write(mFieldData.mOuterRadiusX); // Foliage Outer Radius X.
|
|
stream->write(mFieldData.mOuterRadiusY); // Foliage Outer Radius Y.
|
|
|
|
stream->write(mFieldData.mMinWidth); // Foliage Minimum Width.
|
|
stream->write(mFieldData.mMaxWidth); // Foliage Maximum Width.
|
|
stream->write(mFieldData.mMinHeight); // Foliage Minimum Height.
|
|
stream->write(mFieldData.mMaxHeight); // Foliage Maximum Height.
|
|
stream->write(mFieldData.mFixAspectRatio); // Foliage Fix Aspect Ratio.
|
|
stream->write(mFieldData.mFixSizeToMax); // Foliage Fix Size to Max.
|
|
stream->write(mFieldData.mOffsetZ); // Foliage Offset Z.
|
|
stream->writeFlag(mFieldData.mRandomFlip); // Foliage Random Flip.
|
|
stream->writeFlag(mFieldData.mUseTrueBillboards); // Foliage faces the camera (including z axis)
|
|
|
|
stream->write(mFieldData.mUseCulling); // Foliage Use Culling.
|
|
stream->write(mFieldData.mCullResolution); // Foliage Cull Resolution.
|
|
stream->write(mFieldData.mViewDistance); // Foliage View Distance.
|
|
stream->write(mFieldData.mViewClosest); // Foliage View Closest.
|
|
stream->write(mFieldData.mFadeInRegion); // Foliage Fade-In Region.
|
|
stream->write(mFieldData.mFadeOutRegion); // Foliage Fade-Out Region.
|
|
stream->write(mFieldData.mAlphaCutoff); // Foliage Alpha Cutoff.
|
|
stream->write(mFieldData.mGroundAlpha); // Foliage Ground Alpha.
|
|
|
|
stream->writeFlag(mFieldData.mSwayOn); // Foliage Sway On Flag.
|
|
stream->writeFlag(mFieldData.mSwaySync); // Foliage Sway Sync Flag.
|
|
stream->write(mFieldData.mSwayMagnitudeSide); // Foliage Sway Magnitude Side2Side.
|
|
stream->write(mFieldData.mSwayMagnitudeFront); // Foliage Sway Magnitude Front2Back.
|
|
stream->write(mFieldData.mMinSwayTime); // Foliage Minimum Sway Time.
|
|
stream->write(mFieldData.mMaxSwayTime); // Foliage Maximum way Time.
|
|
|
|
stream->writeFlag(mFieldData.mLightOn); // Foliage Light On Flag.
|
|
stream->writeFlag(mFieldData.mLightSync); // Foliage Light Sync
|
|
stream->write(mFieldData.mMinLuminance); // Foliage Minimum Luminance.
|
|
stream->write(mFieldData.mMaxLuminance); // Foliage Maximum Luminance.
|
|
stream->write(mFieldData.mLightTime); // Foliage Light Time.
|
|
|
|
stream->writeFlag(mFieldData.mAllowOnTerrain); // Allow on Terrain.
|
|
stream->writeFlag(mFieldData.mAllowStatics); // Allow on Statics.
|
|
stream->writeFlag(mFieldData.mAllowOnWater); // Allow on Water.
|
|
stream->writeFlag(mFieldData.mAllowWaterSurface); // Allow on Water Surface.
|
|
stream->write(mFieldData.mAllowedTerrainSlope); // Foliage Offset Z.
|
|
|
|
stream->writeFlag(mFieldData.mHideFoliage); // Hide Foliage.
|
|
stream->writeFlag(mFieldData.mShowPlacementArea); // Show Placement Area Flag.
|
|
stream->write(mFieldData.mPlacementBandHeight); // Placement Area Height.
|
|
stream->write(mFieldData.mPlaceAreaColour); // Placement Area Colour.
|
|
stream->write(mFieldData.mAmbientModulationBias);
|
|
}
|
|
|
|
// Were done ...
|
|
return(retMask);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxFoliageReplicator::unpackUpdate(NetConnection * con, BitStream * stream)
|
|
{
|
|
// Unpack Parent.
|
|
Parent::unpackUpdate(con, stream);
|
|
|
|
// Read Replication Details.
|
|
if(stream->readFlag())
|
|
{
|
|
MatrixF ReplicatorObjectMatrix;
|
|
|
|
stream->readAffineTransform(&ReplicatorObjectMatrix); // Foliage Master Object Position.
|
|
|
|
mFieldData.mUseDebugInfo = stream->readFlag(); // Foliage Debug Information Flag.
|
|
stream->read(&mFieldData.mDebugBoxHeight); // Foliage Debug Height.
|
|
stream->read(&mFieldData.mSeed); // Foliage Seed.
|
|
stream->read(&mFieldData.mFoliageCount); // Foliage Count.
|
|
stream->read(&mFieldData.mFoliageRetries); // Foliage Retries.
|
|
mFieldData.mFoliageFile = stream->readSTString(); // Foliage File.
|
|
|
|
stream->read(&mFieldData.mInnerRadiusX); // Foliage Inner Radius X.
|
|
stream->read(&mFieldData.mInnerRadiusY); // Foliage Inner Radius Y.
|
|
stream->read(&mFieldData.mOuterRadiusX); // Foliage Outer Radius X.
|
|
stream->read(&mFieldData.mOuterRadiusY); // Foliage Outer Radius Y.
|
|
|
|
stream->read(&mFieldData.mMinWidth); // Foliage Minimum Width.
|
|
stream->read(&mFieldData.mMaxWidth); // Foliage Maximum Width.
|
|
stream->read(&mFieldData.mMinHeight); // Foliage Minimum Height.
|
|
stream->read(&mFieldData.mMaxHeight); // Foliage Maximum Height.
|
|
stream->read(&mFieldData.mFixAspectRatio); // Foliage Fix Aspect Ratio.
|
|
stream->read(&mFieldData.mFixSizeToMax); // Foliage Fix Size to Max.
|
|
stream->read(&mFieldData.mOffsetZ); // Foliage Offset Z.
|
|
mFieldData.mRandomFlip = stream->readFlag(); // Foliage Random Flip.
|
|
|
|
bool wasTrueBB = mFieldData.mUseTrueBillboards;
|
|
mFieldData.mUseTrueBillboards = stream->readFlag(); // Foliage is camera facing (including z axis).
|
|
|
|
stream->read(&mFieldData.mUseCulling); // Foliage Use Culling.
|
|
stream->read(&mFieldData.mCullResolution); // Foliage Cull Resolution.
|
|
stream->read(&mFieldData.mViewDistance); // Foliage View Distance.
|
|
stream->read(&mFieldData.mViewClosest); // Foliage View Closest.
|
|
stream->read(&mFieldData.mFadeInRegion); // Foliage Fade-In Region.
|
|
stream->read(&mFieldData.mFadeOutRegion); // Foliage Fade-Out Region.
|
|
stream->read(&mFieldData.mAlphaCutoff); // Foliage Alpha Cutoff.
|
|
stream->read(&mFieldData.mGroundAlpha); // Foliage Ground Alpha.
|
|
|
|
mFieldData.mSwayOn = stream->readFlag(); // Foliage Sway On Flag.
|
|
mFieldData.mSwaySync = stream->readFlag(); // Foliage Sway Sync Flag.
|
|
stream->read(&mFieldData.mSwayMagnitudeSide); // Foliage Sway Magnitude Side2Side.
|
|
stream->read(&mFieldData.mSwayMagnitudeFront); // Foliage Sway Magnitude Front2Back.
|
|
stream->read(&mFieldData.mMinSwayTime); // Foliage Minimum Sway Time.
|
|
stream->read(&mFieldData.mMaxSwayTime); // Foliage Maximum way Time.
|
|
|
|
mFieldData.mLightOn = stream->readFlag(); // Foliage Light On Flag.
|
|
mFieldData.mLightSync = stream->readFlag(); // Foliage Light Sync
|
|
stream->read(&mFieldData.mMinLuminance); // Foliage Minimum Luminance.
|
|
stream->read(&mFieldData.mMaxLuminance); // Foliage Maximum Luminance.
|
|
stream->read(&mFieldData.mLightTime); // Foliage Light Time.
|
|
|
|
mFieldData.mAllowOnTerrain = stream->readFlag(); // Allow on Terrain.
|
|
mFieldData.mAllowStatics = stream->readFlag(); // Allow on Statics.
|
|
mFieldData.mAllowOnWater = stream->readFlag(); // Allow on Water.
|
|
mFieldData.mAllowWaterSurface = stream->readFlag(); // Allow on Water Surface.
|
|
stream->read(&mFieldData.mAllowedTerrainSlope); // Allowed Terrain Slope.
|
|
|
|
mFieldData.mHideFoliage = stream->readFlag(); // Hide Foliage.
|
|
mFieldData.mShowPlacementArea = stream->readFlag(); // Show Placement Area Flag.
|
|
stream->read(&mFieldData.mPlacementBandHeight); // Placement Area Height.
|
|
stream->read(&mFieldData.mPlaceAreaColour);
|
|
|
|
stream->read(&mFieldData.mAmbientModulationBias);
|
|
// Calculate Fade-In/Out Gradients.
|
|
mFadeInGradient = 1.0f / mFieldData.mFadeInRegion;
|
|
mFadeOutGradient = 1.0f / mFieldData.mFadeOutRegion;
|
|
|
|
// Set Transform.
|
|
setTransform(ReplicatorObjectMatrix);
|
|
|
|
// Load Foliage Texture on the client.
|
|
if( mFieldData.mFoliageFile != NULL && dStrlen(mFieldData.mFoliageFile) > 0 )
|
|
mFieldData.mFoliageTexture = GFXTexHandle( mFieldData.mFoliageFile, &GFXStaticTextureSRGBProfile, avar("%s() - mFieldData.mFoliageTexture (line %d)", __FUNCTION__, __LINE__) );
|
|
|
|
if ((GFXTextureObject*) mFieldData.mFoliageTexture == NULL)
|
|
Con::printf("fxFoliageReplicator: %s is an invalid or missing foliage texture file.", mFieldData.mFoliageFile);
|
|
|
|
// Set Quad-Tree Box Height Lerp.
|
|
mFrustumRenderSet.mHeightLerp = mFieldData.mDebugBoxHeight;
|
|
|
|
// Create Foliage (if Replication has begun).
|
|
if (mClientReplicationStarted)
|
|
{
|
|
CreateFoliage();
|
|
mDirty = true;
|
|
}
|
|
|
|
if ( isProperlyAdded() && mFieldData.mUseTrueBillboards != wasTrueBB )
|
|
SetupShader();
|
|
}
|
|
}
|