//----------------------------------------------------------------------------- // Torque Game Engine // 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. // //----------------------------------------------------------------------------- #include "dgl/dgl.h" #include "console/consoleTypes.h" #include "core/bitStream.h" #include "math/mRandom.h" #include "math/mathIO.h" #include "terrain/terrData.h" #include "game/gameConnection.h" #include "console/simBase.h" #include "sceneGraph/sceneGraph.h" #include "fxFoliageReplicator.h" //------------------------------------------------------------------------------ // // 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); //------------------------------------------------------------------------------ // // Trig Table Lookups. // //------------------------------------------------------------------------------ static bool mTrigTableInitialised; static F32 mCosTable[720]; static F32 mSinTable[720]; //------------------------------------------------------------------------------ // // Class: fxFoliageRenderList // //------------------------------------------------------------------------------ void fxFoliageRenderList::SetupClipPlanes(SceneState* state, const F32 FarClipPlane) { // Fetch Camera Position. CameraPosition = state->getCameraPosition(); // Calculate Perspective. F32 FarOverNear = FarClipPlane / state->getNearPlane(); // Calculate Clip-Planes. FarPosLeftUp = Point3F( state->getBaseZoneState().frustum[0] * FarOverNear, FarClipPlane, state->getBaseZoneState().frustum[3] * FarOverNear); FarPosLeftDown = Point3F( state->getBaseZoneState().frustum[0] * FarOverNear, FarClipPlane, state->getBaseZoneState().frustum[2] * FarOverNear); FarPosRightUp = Point3F( state->getBaseZoneState().frustum[1] * FarOverNear, FarClipPlane, state->getBaseZoneState().frustum[3] * FarOverNear); FarPosRightDown = Point3F( state->getBaseZoneState().frustum[1] * FarOverNear, FarClipPlane, state->getBaseZoneState().frustum[2] * FarOverNear); // Calculate our World->Object Space Transform. MatrixF InvXForm = state->mModelview; InvXForm.inverse(); // Convert to Object-Space. InvXForm.mulP(FarPosLeftUp); InvXForm.mulP(FarPosLeftDown); InvXForm.mulP(FarPosRightUp); InvXForm.mulP(FarPosRightDown); // Calculate Bounding Box (including Camera). mBox.min = CameraPosition; mBox.min.setMin(FarPosLeftUp); mBox.min.setMin(FarPosLeftDown); mBox.min.setMin(FarPosRightUp); mBox.min.setMin(FarPosRightDown); mBox.max = CameraPosition; mBox.max.setMax(FarPosLeftUp); mBox.max.setMax(FarPosLeftDown); mBox.max.setMax(FarPosRightUp); mBox.max.setMax(FarPosRightDown); // Setup Our Viewplane. ViewPlanes[0].set(CameraPosition, FarPosLeftUp, FarPosLeftDown); ViewPlanes[1].set(CameraPosition, FarPosRightUp, FarPosLeftUp); ViewPlanes[2].set(CameraPosition, FarPosRightDown, FarPosRightUp); ViewPlanes[3].set(CameraPosition, FarPosLeftDown, FarPosRightDown); ViewPlanes[4].set(FarPosLeftUp, FarPosRightUp, FarPosRightDown); } //------------------------------------------------------------------------------ inline void fxFoliageRenderList::DrawQuadBox(const Box3F& QuadBox, const ColorF 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 }; static Point3F BoxNormals[] = { Point3F(-1, 0, 0), Point3F( 1, 0, 0), Point3F( 0,-1, 0), Point3F( 0, 1, 0), Point3F( 0, 0,-1), Point3F( 0, 0, 1) }; // Select the Box Colour. glColor4fv(Colour); // Project our Box Points. Point3F ProjectionPoints[8]; for(U32 i = 0; i < 8; i++) { ProjectionPoints[i].set(BoxPnts[i].x ? QuadBox.max.x : QuadBox.min.x, BoxPnts[i].y ? QuadBox.max.y : QuadBox.min.y, BoxPnts[i].z ? (mHeightLerp * QuadBox.max.z) + (1-mHeightLerp) * QuadBox.min.z : QuadBox.min.z); } // Draw the Box. for(U32 x = 0; x < 6; x++) { // Draw a line-loop. glBegin(GL_LINE_LOOP); for(U32 y = 0; y < 4; y++) { // Draw Vertex. glVertex3f( ProjectionPoints[BoxVerts[x][y]].x, ProjectionPoints[BoxVerts[x][y]].y, ProjectionPoints[BoxVerts[x][y]].z); } glEnd(); } } //------------------------------------------------------------------------------ void fxFoliageRenderList::CompileVisibleSet(const fxFoliageQuadrantNode* pNode, const MatrixF& RenderTransform, const bool UseDebug) { // Attempt to trivially reject the Node. // // Is any of the quadrant visible? if (IsQuadrantVisible(pNode->QuadrantBox, RenderTransform)) { // Draw the Quad Box (Debug Only). if (UseDebug) DrawQuadBox(pNode->QuadrantBox, ColorF(0,.8,.1,.2)); // Yes, so are we at sub-level 0? if (pNode->Level == 0) { // Yes, so merge visible object set with node render list. mVisObjectSet.merge(pNode->RenderList); } else { // No, so compile quadrants. for (U32 q = 0; q < 4; q++) if (pNode->QuadrantChildNode[q]) CompileVisibleSet(pNode->QuadrantChildNode[q], RenderTransform, UseDebug); } } else { // Draw the Quad Box (Debug Only). if (UseDebug) DrawQuadBox(pNode->QuadrantBox, ColorF(0,.1,8,.2)); } return; } //------------------------------------------------------------------------------ bool fxFoliageRenderList::IsQuadrantVisible(const Box3F VisBox, const MatrixF& RenderTransform) { // Can we trivially accept the visible box? if (mBox.isOverlapped(VisBox)) { // Yes, so calculate Object-Space Box. MatrixF InvXForm = RenderTransform; InvXForm.inverse(); Box3F OSBox = VisBox; InvXForm.mulP(OSBox.min); InvXForm.mulP(OSBox.max); // Yes, so fetch Box Center. Point3F Center; OSBox.getCenter(&Center); // Scale. Point3F XRad(OSBox.len_x() * 0.5, 0, 0); Point3F YRad(0, OSBox.len_y() * 0.5, 0); Point3F ZRad(0, 0, OSBox.len_z() * 0.5); // Render Transformation. RenderTransform.mulP(Center); RenderTransform.mulV(XRad); RenderTransform.mulV(YRad); RenderTransform.mulV(ZRad); // Check against View-planes. for (U32 i = 0; i < 5; i++) { // Reject if not visible. if (ViewPlanes[i].whichSideBox(Center, XRad, YRad, ZRad, Point3F(0, 0, 0)) == PlaneF::Back) return false; } // Visible. return true; } // Not visible. return false; } //------------------------------------------------------------------------------ // // Class: fxFoliageCulledList // //------------------------------------------------------------------------------ fxFoliageCulledList::fxFoliageCulledList(Box3F SearchBox, fxFoliageCulledList* InVec) { // Find the Candidates. FindCandidates(SearchBox, InVec); } //------------------------------------------------------------------------------ void fxFoliageCulledList::FindCandidates(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 | StaticTSObjectType | StaticRenderedObjectType; mAddedToScene = false; mNetFlags.set(Ghostable | ScopeAlways); // Reset Client Replication Started. mClientReplicationStarted = false; // Reset Foliage Count. mCurrentFoliageCount = 0; // 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; } //------------------------------------------------------------------------------ fxFoliageReplicator::~fxFoliageReplicator() { } //------------------------------------------------------------------------------ void fxFoliageReplicator::initPersistFields() { // Initialise parents' persistent fields. Parent::initPersistFields(); // Add out own persistent fields. addGroup( "Debugging" ); addField( "UseDebugInfo", TypeBool, Offset( mFieldData.mUseDebugInfo, fxFoliageReplicator ) ); addField( "DebugBoxHeight", TypeF32, Offset( mFieldData.mDebugBoxHeight, fxFoliageReplicator ) ); addField( "HideFoliage", TypeBool, Offset( mFieldData.mHideFoliage, fxFoliageReplicator ) ); addField( "ShowPlacementArea", TypeBool, Offset( mFieldData.mShowPlacementArea, fxFoliageReplicator ) ); addField( "PlacementAreaHeight",TypeS32, Offset( mFieldData.mPlacementBandHeight, fxFoliageReplicator ) ); addField( "PlacementColour", TypeColorF, Offset( mFieldData.mPlaceAreaColour, fxFoliageReplicator ) ); endGroup( "Debugging" ); addGroup( "Media" ); addField( "Seed", TypeS32, Offset( mFieldData.mSeed, fxFoliageReplicator ) ); addField( "FoliageFile", TypeFilename, Offset( mFieldData.mFoliageFile, fxFoliageReplicator ) ); addField( "FoliageCount", TypeS32, Offset( mFieldData.mFoliageCount, fxFoliageReplicator ) ); addField( "FoliageRetries", TypeS32, Offset( mFieldData.mFoliageRetries, fxFoliageReplicator ) ); endGroup( "Media" ); addGroup( "Area" ); addField( "InnerRadiusX", TypeS32, Offset( mFieldData.mInnerRadiusX, fxFoliageReplicator ) ); addField( "InnerRadiusY", TypeS32, Offset( mFieldData.mInnerRadiusY, fxFoliageReplicator ) ); addField( "OuterRadiusX", TypeS32, Offset( mFieldData.mOuterRadiusX, fxFoliageReplicator ) ); addField( "OuterRadiusY", TypeS32, Offset( mFieldData.mOuterRadiusY, fxFoliageReplicator ) ); endGroup( "Area" ); addGroup( "Dimensions" ); addField( "MinWidth", TypeF32, Offset( mFieldData.mMinWidth, fxFoliageReplicator ) ); addField( "MaxWidth", TypeF32, Offset( mFieldData.mMaxWidth, fxFoliageReplicator ) ); addField( "MinHeight", TypeF32, Offset( mFieldData.mMinHeight, fxFoliageReplicator ) ); addField( "MaxHeight", TypeF32, Offset( mFieldData.mMaxHeight, fxFoliageReplicator ) ); addField( "FixAspectRatio", TypeBool, Offset( mFieldData.mFixAspectRatio, fxFoliageReplicator ) ); addField( "FixSizeToMax", TypeBool, Offset( mFieldData.mFixSizeToMax, fxFoliageReplicator ) ); addField( "OffsetZ", TypeF32, Offset( mFieldData.mOffsetZ, fxFoliageReplicator ) ); addField( "RandomFlip", TypeBool, Offset( mFieldData.mRandomFlip, fxFoliageReplicator ) ); endGroup( "Dimensions" ); addGroup( "Culling" ); addField( "UseCulling", TypeBool, Offset( mFieldData.mUseCulling, fxFoliageReplicator ) ); addField( "CullResolution", TypeS32, Offset( mFieldData.mCullResolution, fxFoliageReplicator ) ); addField( "ViewDistance", TypeF32, Offset( mFieldData.mViewDistance, fxFoliageReplicator ) ); addField( "ViewClosest", TypeF32, Offset( mFieldData.mViewClosest, fxFoliageReplicator ) ); addField( "FadeInRegion", TypeF32, Offset( mFieldData.mFadeInRegion, fxFoliageReplicator ) ); addField( "FadeOutRegion", TypeF32, Offset( mFieldData.mFadeOutRegion, fxFoliageReplicator ) ); addField( "AlphaCutoff", TypeF32, Offset( mFieldData.mAlphaCutoff, fxFoliageReplicator ) ); addField( "GroundAlpha", TypeF32, Offset( mFieldData.mGroundAlpha, fxFoliageReplicator ) ); endGroup( "Culling" ); addGroup( "Animation" ); addField( "SwayOn", TypeBool, Offset( mFieldData.mSwayOn, fxFoliageReplicator ) ); addField( "SwaySync", TypeBool, Offset( mFieldData.mSwaySync, fxFoliageReplicator ) ); addField( "SwayMagSide", TypeF32, Offset( mFieldData.mSwayMagnitudeSide, fxFoliageReplicator ) ); addField( "SwayMagFront", TypeF32, Offset( mFieldData.mSwayMagnitudeFront, fxFoliageReplicator ) ); addField( "MinSwayTime", TypeF32, Offset( mFieldData.mMinSwayTime, fxFoliageReplicator ) ); addField( "MaxSwayTime", TypeF32, Offset( mFieldData.mMaxSwayTime, fxFoliageReplicator ) ); endGroup( "Animation" ); addGroup( "Lighting" ); addField( "LightOn", TypeBool, Offset( mFieldData.mLightOn, fxFoliageReplicator ) ); addField( "LightSync", TypeBool, Offset( mFieldData.mLightSync, fxFoliageReplicator ) ); addField( "MinLuminance", TypeF32, Offset( mFieldData.mMinLuminance, fxFoliageReplicator ) ); addField( "MaxLuminance", TypeF32, Offset( mFieldData.mMaxLuminance, fxFoliageReplicator ) ); addField( "LightTime", TypeF32, Offset( mFieldData.mLightTime, fxFoliageReplicator ) ); endGroup( "Lighting" ); addGroup( "Restrictions" ); addField( "AllowOnTerrain", TypeBool, Offset( mFieldData.mAllowOnTerrain, fxFoliageReplicator ) ); addField( "AllowOnInteriors", TypeBool, Offset( mFieldData.mAllowOnInteriors, fxFoliageReplicator ) ); addField( "AllowOnStatics", TypeBool, Offset( mFieldData.mAllowStatics, fxFoliageReplicator ) ); addField( "AllowOnWater", TypeBool, Offset( mFieldData.mAllowOnWater, fxFoliageReplicator ) ); addField( "AllowWaterSurface", TypeBool, Offset( mFieldData.mAllowWaterSurface, fxFoliageReplicator ) ); addField( "AllowedTerrainSlope",TypeS32, Offset( mFieldData.mAllowedTerrainSlope, fxFoliageReplicator ) ); endGroup( "Restrictions" ); } //------------------------------------------------------------------------------ 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 (mFieldData.mFoliageFile == "") return; // Check that we can position somewhere! if (!( mFieldData.mAllowOnTerrain || mFieldData.mAllowOnInteriors || 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 * ( (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 = 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, n))); // Ceil to be safe! // ---------------------------------------------------------------------------------------------------------------------- // Step 2. // ---------------------------------------------------------------------------------------------------------------------- // Set Seed. RandomGen.setSeed(mFieldData.mSeed); // Have we setup the Trig Tables? if (!mTrigTableInitialised) { F32 tIdx = 0.0f; // No, so setup Tables. for (U32 idx = 0; idx < 720; idx++, tIdx+=M_2PI/720.0f) { mCosTable[idx] = mCos(tIdx); mSinTable[idx] = mSin(tIdx); } // Signal Trig Tables Initialised. mTrigTableInitialised = true; } // Add Foliage. for (U32 idx = 0; idx < mFieldData.mFoliageCount; idx++) { fxFoliageItem* pFoliageItem; // Reset Relocation Retry. RelocationRetry = mFieldData.mFoliageRetries; // Find it a home ... do { // Calculate a random offset HypX = RandomGen.randF(mFieldData.mInnerRadiusX, mFieldData.mOuterRadiusX); HypY = RandomGen.randF(mFieldData.mInnerRadiusY, mFieldData.mOuterRadiusY); Angle = RandomGen.randF(0, M_2PI); // Calculate the new position. Point3F randomShapePosLocal; randomShapePosLocal.x = HypX * mCos(Angle); randomShapePosLocal.y = HypY * mSin(Angle); // Transform into world space coordinates Point3F shapePosWorld; MatrixF objToWorld = getRenderTransform(); objToWorld.mulP(randomShapePosLocal, &shapePosWorld); FoliagePosition = shapePosWorld; // 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 & InteriorObjectType) && !mFieldData.mAllowOnInteriors) || ((CollisionType & StaticTSObjectType) && !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; } // 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.min = FoliagePosition + Point3F(-pFoliageItem->Width / 2.0f - mFieldData.mSwayMagnitudeSide, -0.5f - mFieldData.mSwayMagnitudeFront, pFoliageItem->Height ); pFoliageItem->FoliageBox.max = FoliagePosition + Point3F(+pFoliageItem->Width / 2.0f + mFieldData.mSwayMagnitudeSide, +0.5f + mFieldData.mSwayMagnitudeFront, pFoliageItem->Height ); } else { // No, so give it a minimum volume... pFoliageItem->FoliageBox.min = FoliagePosition + Point3F(-pFoliageItem->Width / 2.0f, -0.5f, pFoliageItem->Height ); pFoliageItem->FoliageBox.max = FoliagePosition + Point3F(+pFoliageItem->Width / 2.0f, +0.5f, pFoliageItem->Height ); } // Monitor the total volume. MinPoint.setMin( pFoliageItem->FoliageBox.min - getRenderPosition() ); MaxPoint.setMax( pFoliageItem->FoliageBox.max - getRenderPosition() ); // Store Shape in Replicated Shapes Vector. mReplicatedFoliage.push_back(pFoliageItem); // Increase Foliage Count. mCurrentFoliageCount++; } // Is Lighting On? if (mFieldData.mLightOn) { // 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) { // Yes, so reset Global Light phase. mGlobalLightPhase = 0.0f; // Set Global Light Time Ratio. mGlobalLightTimeRatio = 719.0f / mFieldData.mLightTime; } else { // No, so choose a random Light phase. pFoliageItem->LightPhase = RandomGen.randF(0, 719.0f); // Set Light Time Ratio. pFoliageItem->LightTimeRatio = 719.0f / mFieldData.mLightTime; } } } } // Is Swaying Enabled? if (mFieldData.mSwayOn) { // 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) { // Yes, so reset Global Sway phase. mGlobalSwayPhase = 0.0f; // Set Global Sway Time Ratio. mGlobalSwayTimeRatio = 719.0f / RandomGen.randF(mFieldData.mMinSwayTime, mFieldData.mMaxSwayTime); } else { // No, so choose a random Sway phase. pFoliageItem->SwayPhase = RandomGen.randF(0, 719.0f); // Set to random Sway Time. pFoliageItem->SwayTimeRatio = 719.0f / RandomGen.randF(mFieldData.mMinSwayTime, mFieldData.mMaxSwayTime); } } } } // Update our Object Volume. mObjBox.min.set(MinPoint); mObjBox.max.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 = (mNextAllocatedNodeIdx-1) * sizeof(fxFoliageQuadrantNode); MemoryAllocated += mCurrentFoliageCount * sizeof(fxFoliageItem); MemoryAllocated += mCurrentFoliageCount * sizeof(fxFoliageItem*); Con::printf("fxFoliageReplicator - Approx. %0.2fMb allocated.", MemoryAllocated / 1048576.0f); // ---------------------------------------------------------------------------------------------------------------------- // Take first Timestamp. mLastRenderTime = Platform::getVirtualMilliseconds(); } //------------------------------------------------------------------------------ Box3F fxFoliageReplicator::FetchQuadrant(Box3F Box, U32 Quadrant) { Box3F QuadrantBox; // Select Quadrant. switch(Quadrant) { // UL. case 0: QuadrantBox.min = Box.min + Point3F(0, Box.len_y()/2, 0); QuadrantBox.max = QuadrantBox.min + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z()); break; // UR. case 1: QuadrantBox.min = Box.min + Point3F(Box.len_x()/2, Box.len_y()/2, 0); QuadrantBox.max = QuadrantBox.min + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z()); break; // LL. case 2: QuadrantBox.min = Box.min; QuadrantBox.max = QuadrantBox.min + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z()); break; // LR. case 3: QuadrantBox.min = Box.min + Point3F(Box.len_x()/2, 0, 0); QuadrantBox.max = QuadrantBox.min + 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(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(*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."); } //------------------------------------------------------------------------------ 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; // Remove shapes. for (U32 idx = 0; idx < mCurrentFoliageCount; idx++) { fxFoliageItem* pFoliageItem; // Fetch the Foliage Item. pFoliageItem = mReplicatedFoliage[idx]; // Delete Shape. if (pFoliageItem) delete pFoliageItem; } // Let's remove the Quad-Tree allocations. for ( Vector::iterator QuadNodeItr = mFoliageQuadTree.begin(); QuadNodeItr != mFoliageQuadTree.end(); QuadNodeItr++ ) { // Remove the node. delete *QuadNodeItr; } // Clear the Foliage Quad-Tree Vector. mFoliageQuadTree.clear(); // Clear the Replicated Foliage Vector. mReplicatedFoliage.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(Sim::findObject("fxFoliageSet"))->addObject(this); // Set Default Object Box. mObjBox.min.set( -0.5, -0.5, -0.5 ); mObjBox.max.set( 0.5, 0.5, 0.5 ); // Reset the World Box. resetWorldBox(); // Set the Render Transform. setRenderTransform(mObjToWorld); // Add to Scene. addToScene(); mAddedToScene = true; // Are we on the client? if ( isClientObject() ) { // Yes, so load foliage texture. mFieldData.mFoliageTexture = TextureHandle( mFieldData.mFoliageFile, MeshTexture ); // If we are in the editor then we can manually startup replication. if (gEditingMission) mClientReplicationStarted = true; } // Return OK. return(true); } //------------------------------------------------------------------------------ void fxFoliageReplicator::onRemove() { // Remove the Replicator from the Replicator Set. dynamic_cast(Sim::findObject("fxFoliageSet"))->removeObject(this); // Remove from Scene. removeFromScene(); mAddedToScene = false; // Are we on the Client? if (isClientObject()) { // Yes, so destroy Foliage. DestroyFoliage(); // Remove Texture. mFieldData.mFoliageTexture = NULL; } // Do Parent. Parent::onRemove(); } //------------------------------------------------------------------------------ void fxFoliageReplicator::inspectPostApply() { // Set Parent. Parent::inspectPostApply(); // Set Foliage Replication Mask (this object only). setMaskBits(FoliageReplicationMask); } //------------------------------------------------------------------------------ void fxFoliageReplicator::onEditorEnable() { } //------------------------------------------------------------------------------ void fxFoliageReplicator::onEditorDisable() { } //------------------------------------------------------------------------------ ConsoleFunction(StartFoliageReplication, void, 1, 1, "StartFoliageReplication()") { // Find the Replicator Set. SimSet *fxFoliageSet = dynamic_cast(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(*itr); // Start Client Objects Only. if (Replicator->isClientObject()) Replicator->StartUp(); } // Info ... Con::printf("fxFoliageReplicator - Client Foliage Replication Startup is complete."); } //------------------------------------------------------------------------------ bool fxFoliageReplicator::prepRenderImage(SceneState* state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState) { // Return if last state. if (isLastState(state, stateKey)) return false; // Set Last State. setLastState(state, stateKey); // Is Object Rendered? if (state->isObjectRendered(this)) { // Yes, so get a SceneRenderImage. SceneRenderImage* image = new SceneRenderImage; // Populate it. image->obj = this; image->sortType = SceneRenderImage::Point; image->isTranslucent = true; image->poly[0] = getPosition(); // Insert it into the scene images. state->insertRenderImage(image); } return false; } //------------------------------------------------------------------------------ void fxFoliageReplicator::renderObject(SceneState* state, SceneRenderImage*) { // Check we are in Canonical State. AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); // Calculate Elapsed Time and take new Timestamp. S32 Time = Platform::getVirtualMilliseconds(); F32 ElapsedTime = (Time - mLastRenderTime) * 0.001f; mLastRenderTime = Time; // Setup out the Projection Matrix/Viewport. RectI viewport; glMatrixMode(GL_PROJECTION); glPushMatrix(); dglGetViewport(&viewport); state->setupBaseProjection(); // Draw Placement Area (if needed and Editing Mission). if (gEditingMission && mFieldData.mShowPlacementArea) { // Setup our rendering state. glPushMatrix(); dglMultMatrix(&getTransform()); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // Do we need to draw the Inner Radius? if (mFieldData.mInnerRadiusX || mFieldData.mInnerRadiusY) { // Yes, so draw Inner Radius. glBegin(GL_TRIANGLE_STRIP); for (U32 Angle = mCreationAreaAngle; Angle < (mCreationAreaAngle+360); Angle++) { F32 XPos, YPos; // Calculate Position. XPos = mFieldData.mInnerRadiusX * mCos(mDegToRad(-(F32)Angle)); YPos = mFieldData.mInnerRadiusY * mSin(mDegToRad(-(F32)Angle)); // Set Colour. glColor4f( mFieldData.mPlaceAreaColour.red, mFieldData.mPlaceAreaColour.green, mFieldData.mPlaceAreaColour.blue, AREA_ANIMATION_ARC * (Angle-mCreationAreaAngle)); // Draw Arc Line. glVertex3f( XPos, YPos, -(F32)mFieldData.mPlacementBandHeight/2.0f); glVertex3f( XPos, YPos, +(F32)mFieldData.mPlacementBandHeight/2.0f); } glEnd(); } // Do we need to draw the Outer Radius? if (mFieldData.mOuterRadiusX || mFieldData.mOuterRadiusY) { // Yes, so draw Outer Radius. glBegin(GL_TRIANGLE_STRIP); for (U32 Angle = mCreationAreaAngle; Angle < (mCreationAreaAngle+360); Angle++) { F32 XPos, YPos; // Calculate Position. XPos = mFieldData.mOuterRadiusX * mCos(mDegToRad(-(F32)Angle)); YPos = mFieldData.mOuterRadiusY * mSin(mDegToRad(-(F32)Angle)); // Set Colour. glColor4f( mFieldData.mPlaceAreaColour.red, mFieldData.mPlaceAreaColour.green, mFieldData.mPlaceAreaColour.blue, AREA_ANIMATION_ARC * (Angle-mCreationAreaAngle)); // Draw Arc Line. glVertex3f( XPos, YPos, -(F32)mFieldData.mPlacementBandHeight/2.0f); glVertex3f( XPos, YPos, +(F32)mFieldData.mPlacementBandHeight/2.0f); } glEnd(); } // Restore rendering state. glDisable(GL_BLEND); glPopMatrix(); // Animate Area Selection. mCreationAreaAngle = (U32)(mCreationAreaAngle + (1000 * ElapsedTime)); mCreationAreaAngle = mCreationAreaAngle % 360; } // Draw Foliage. if (!mFieldData.mHideFoliage && mCurrentFoliageCount) { // Calculate some constants. const F32 ClippedViewDistance = mFieldData.mViewDistance; const F32 MinimumViewDistance = mFieldData.mViewClosest - mFieldData.mFadeOutRegion; const F32 MaximumViewDistance = ClippedViewDistance + mFieldData.mFadeInRegion; const F32 LuminanceMidPoint = (mFieldData.mMinLuminance + mFieldData.mMaxLuminance) / 2.0f; const F32 LuminanceMagnitude = mFieldData.mMaxLuminance - LuminanceMidPoint; // Billboard Details. MatrixF ModelView; Point4F Position; const Point4F XRotation(1,0,0,0); const Point4F YRotation(0,1,0,0); Point4F ZRotation; F32 LeftTexPos; F32 RightTexPos; // Sway Luminance. F32 Luminance = 1.0f; // Reset Sway Offsets. F32 SwayOffsetX = 0.0f; F32 SwayOffsetY = 0.0f; // Is Swaying On and *in* Sync? if (mFieldData.mSwayOn && mFieldData.mSwaySync) { // Yes, so calculate Global Sway Offset. SwayOffsetX = mFieldData.mSwayMagnitudeSide * mCosTable[(U32)mGlobalSwayPhase]; SwayOffsetY = mFieldData.mSwayMagnitudeFront * mSinTable[(U32)mGlobalSwayPhase]; // Animate Global Sway Phase (Modulus). mGlobalSwayPhase = mGlobalSwayPhase + (mGlobalSwayTimeRatio * ElapsedTime); if (mGlobalSwayPhase >= 720.0f) mGlobalSwayPhase -= 720.0f; } // Is Light On and *in* Sync? if (mFieldData.mLightOn && mFieldData.mLightSync) { // Yes, so calculate Global Light Luminance. Luminance = LuminanceMidPoint + LuminanceMagnitude * mCosTable[(U32)mGlobalLightPhase]; // Animate Global Light Phase (Modulus). mGlobalLightPhase = mGlobalLightPhase + (mGlobalLightTimeRatio * ElapsedTime); if (mGlobalLightPhase >= 720.0f) mGlobalLightPhase -= 720.0f; } // Are we using culling? if (mFieldData.mUseCulling) { // Yes, so clear the Render Object Set. mFrustumRenderSet.mVisObjectSet.clear(); // Calculate nearest Clipping Far-Plane. // // NOTE:- Here we want the nearest plane to which we want to clip. // This will be either the 'mViewDistance'/'mFadeInRegion' or the // frustum FarPlane. F32 FarClipPlane = getMin((F32)state->getFarPlane(), mFieldData.mViewDistance + mFieldData.mFadeInRegion); // Setup the Clip-Planes. mFrustumRenderSet.SetupClipPlanes(state, FarClipPlane); if ( mFieldData.mUseDebugInfo) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); } // Compile the Visible Set. mFrustumRenderSet.CompileVisibleSet(mFoliageQuadTree[0], getRenderTransform(), mFieldData.mUseDebugInfo); if ( mFieldData.mUseDebugInfo) { // Restore rendering state. glDisable(GL_BLEND); } } else { // No, so clear the Render Object Set. mFrustumRenderSet.mVisObjectSet.clear(); // Handle *all* objects ... potential eeek! mFrustumRenderSet.mVisObjectSet.merge(mReplicatedFoliage); } // Increase Frame Serial ID. mFrameSerialID++; // Reset Foliage Processed Counter. U32 FoliageProcessed = 0; // Only process if we have any trivially visible objects. if (mFrustumRenderSet.mVisObjectSet.size() > 0) { // Setup Render State. glEnable ( GL_TEXTURE_2D ); glBindTexture ( GL_TEXTURE_2D, mFieldData.mFoliageTexture.getGLName() ); glEnable ( GL_BLEND ); glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ); glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); glEnable ( GL_ALPHA_TEST ); glEnable ( GL_CULL_FACE ); glAlphaFunc ( GL_GREATER, mFieldData.mAlphaCutoff ); // Step through Foliage. for (U32 idx = 0; idx < mFrustumRenderSet.mVisObjectSet.size(); idx++) { fxFoliageItem* pFoliageItem; F32 Width, Height; F32 Distance; F32 ItemAlpha; F32 FogAlpha; // Fetch the Foliage Item. pFoliageItem = mFrustumRenderSet.mVisObjectSet[idx]; // Check to see if we've already rendered this object. // // NOTE:- We need to do this as objects can overlap over quad-tree boundaries and are // therefore picked up. We don't want to remove these duplicates otherwise the // overlapping billboards would be contained within a single quad-block only // and could possibly be culled even though they are visible in the adjacent // quad-block. In other words, billboards are *not* treated as a point but rather // a psuedo volume which stops them popping-up when the central point is visible. // // We also mark the billboard as rendered even if it results in being culled // further on as this stops it being rendered further this frame. // // Has the item been rendered this frame? if (pFoliageItem->LastFrameSerialID != mFrameSerialID) { // No, so flag as rendered this frame. pFoliageItem->LastFrameSerialID = mFrameSerialID; // Increase Foliage Processed Counter. FoliageProcessed++; // Is Swaying On and *not* in Sync? if (mFieldData.mSwayOn && !mFieldData.mSwaySync) { // Yes, so calculate Sway Offset. SwayOffsetX = mFieldData.mSwayMagnitudeSide * mCosTable[(U32)pFoliageItem->SwayPhase]; SwayOffsetY = mFieldData.mSwayMagnitudeFront * mSinTable[(U32)pFoliageItem->SwayPhase]; // Animate Sway Phase (Modulus). pFoliageItem->SwayPhase = pFoliageItem->SwayPhase + (pFoliageItem->SwayTimeRatio * ElapsedTime); if (pFoliageItem->SwayPhase >= 720.0f) pFoliageItem->SwayPhase -= 720.0f; } // Is Light On and *not* in Sync? if (mFieldData.mLightOn && !mFieldData.mLightSync) { // Yes, so calculate Light Luminance. Luminance = LuminanceMidPoint + LuminanceMagnitude * mCosTable[(U32)pFoliageItem->LightPhase]; // Animate Light Phase (Modulus). pFoliageItem->LightPhase = pFoliageItem->LightPhase + (pFoliageItem->LightTimeRatio * ElapsedTime); if (pFoliageItem->LightPhase >= 720.0f) pFoliageItem->LightPhase -= 720.0f; } // Calculate Distance to Item. Distance = (pFoliageItem->Transform.getPosition() - state->getCameraPosition()).len(); // Trivially reject the billboard if it's beyond the SceneGraphs visible distance. if (Distance > state->getVisibleDistance()) continue; // Calculate Fog Alpha. FogAlpha = 1.0f - state->getHazeAndFog(Distance, pFoliageItem->Transform.getPosition().z - state->getCameraPosition().z); // Trivially reject the billboard if it's totally transparent. if (FogAlpha < FXFOLIAGE_ALPHA_EPSILON) continue; // Can we trivially accept the billboard? if (Distance >= MinimumViewDistance && Distance <= MaximumViewDistance) { // Yes, so 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); } // No, so set full. else { ItemAlpha = 1.0f; } // Clamp upper-limit to Fog Alpha. if (ItemAlpha > FogAlpha) ItemAlpha = FogAlpha; // Store our Modelview. glPushMatrix(); // Perform Spherical Billboarding. dglMultMatrix(&pFoliageItem->Transform); dglGetModelview(&ModelView); ModelView.setColumn(0, XRotation); ModelView.setColumn(1, YRotation); dglLoadMatrix(&ModelView); // Fetch Width/Height. Width = pFoliageItem->Width / 2.0f; Height = pFoliageItem->Height; // Fetch Flipped Flag. LeftTexPos = pFoliageItem->Flipped ? 1.0f : 0.0f; RightTexPos = 1.0f - LeftTexPos; // Draw Billboard. glBegin(GL_QUADS); // Set Blend Function. glColor4f(Luminance,Luminance,Luminance, ItemAlpha); // Draw Top part of billboard. glTexCoord2f (LeftTexPos,0); glVertex3f (-Width+SwayOffsetX,SwayOffsetY,Height); glTexCoord2f (RightTexPos,0); glVertex3f (+Width+SwayOffsetX,SwayOffsetY,Height); // Set Ground Blend. if (mFieldData.mGroundAlpha < 1.0f) glColor4f(Luminance, Luminance, Luminance, mFieldData.mGroundAlpha < ItemAlpha ? mFieldData.mGroundAlpha : ItemAlpha); // Draw bottom part of billboard. glTexCoord2f (RightTexPos,1); glVertex3f (+Width,0,0); glTexCoord2f (LeftTexPos,1); glVertex3f (-Width,0,0); glEnd(); // Restore our Modelview. glPopMatrix(); } } } // Restore rendering state. glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); glDisable ( GL_CULL_FACE ); glDisable ( GL_ALPHA_TEST ); glDisable ( GL_BLEND ); glDisable ( GL_TEXTURE_2D ); } // Dump Debug Information (Uncomment this if needed). // // NOTE:- Here we show the number of objects rendered compared to the potential objects in the viewing frustum. // Remember that the difference is simply billboards that intersect quad-tree boundaries. // //if (mFieldData.mUseDebugInfo) Con::printf("fxFoliageReplicator - %d of %d Foliage Items Processed", FoliageProcessed, mFrustumRenderSet.mVisObjectSet.size()); } // Restore out nice and friendly canonical state. glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); dglSetViewport(viewport); // Check we have restored Canonical State. AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); } //------------------------------------------------------------------------------ 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->write(mFieldData.mRandomFlip); // Foliage Random Flip. 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.mAllowOnInteriors); // Allow on Interiors. 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. } // 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. stream->read(&mFieldData.mRandomFlip); // Foliage Random Flip. 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.mAllowOnInteriors = stream->readFlag(); // Allow on Interiors. 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); // 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. mFieldData.mFoliageTexture = TextureHandle( mFieldData.mFoliageFile, MeshTexture ); // Set Quad-Tree Box Height Lerp. mFrustumRenderSet.mHeightLerp = mFieldData.mDebugBoxHeight; // Create Foliage (if Replication has begun). if (mClientReplicationStarted) CreateFoliage(); } }