Torque3D/Engine/source/T3D/groundPlane.cpp

608 lines
18 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.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// 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/groundPlane.h"
#include "renderInstance/renderPassManager.h"
#include "scene/sceneRenderState.h"
#include "materials/sceneData.h"
#include "materials/materialDefinition.h"
#include "materials/materialManager.h"
#include "materials/baseMatInstance.h"
#include "math/util/frustum.h"
#include "math/mPlane.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "core/stream/bitStream.h"
#include "collision/boxConvex.h"
#include "collision/abstractPolyList.h"
#include "T3D/physics/physicsPlugin.h"
#include "T3D/physics/physicsBody.h"
#include "T3D/physics/physicsCollision.h"
#ifdef TORQUE_AFX_ENABLED
#include "afx/ce/afxZodiacMgr.h"
#endif
/// Minimum square size allowed. This is a cheap way to limit the amount
/// of geometry possibly generated by the GroundPlane (vertex buffers have a
/// limit, too). Dynamically clipping extents into range is a problem since the
/// location of the horizon depends on the camera orientation. Just shifting
/// squareSize as needed also doesn't work as that causes different geometry to
/// be generated depending on the viewpoint and orientation which affects the
/// texturing.
static const F32 sMIN_SQUARE_SIZE = 16;
IMPLEMENT_CO_NETOBJECT_V1( GroundPlane );
ConsoleDocClass( GroundPlane,
"@brief An infinite plane extending in all direction.\n\n"
"%GroundPlane is useful for setting up simple testing scenes, or it can be "
"placed under an existing scene to keep objects from falling into 'nothing'.\n\n"
"%GroundPlane may not be moved or rotated, it is always at the world origin.\n\n"
"@ingroup Terrain"
);
GroundPlane::GroundPlane()
: mSquareSize( 128.0f ),
mScaleU( 1.0f ),
mScaleV( 1.0f ),
mMaterial( NULL ),
mMaterialInst(NULL),
mPhysicsRep( NULL ),
mMin( 0.0f, 0.0f ),
mMax( 0.0f, 0.0f )
{
mTypeMask |= StaticObjectType | StaticShapeObjectType;
mNetFlags.set( Ghostable | ScopeAlways );
mConvexList = new Convex;
mTypeMask |= TerrainLikeObjectType;
INIT_ASSET(Material);
}
GroundPlane::~GroundPlane()
{
mMaterial = nullptr;
if(mMaterialInst)
SAFE_DELETE(mMaterialInst);
mConvexList->nukeList();
SAFE_DELETE( mConvexList );
}
void GroundPlane::initPersistFields()
{
addGroup( "Plane" );
addField( "squareSize", TypeF32, Offset( mSquareSize, GroundPlane ), "Square size in meters to which %GroundPlane subdivides its geometry." );
addField( "scaleU", TypeF32, Offset( mScaleU, GroundPlane ), "Scale of texture repeat in the U direction." );
addField( "scaleV", TypeF32, Offset( mScaleV, GroundPlane ), "Scale of texture repeat in the V direction." );
INITPERSISTFIELD_MATERIALASSET(Material, GroundPlane, "The material used to render the ground plane.");
endGroup( "Plane" );
Parent::initPersistFields();
removeField( "scale" );
removeField( "position" );
removeField( "rotation" );
}
bool GroundPlane::onAdd()
{
if( !Parent::onAdd() )
return false;
if( isClientObject() )
_updateMaterial();
if( mSquareSize < sMIN_SQUARE_SIZE )
{
Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE );
mSquareSize = sMIN_SQUARE_SIZE;
}
Parent::setScale( VectorF( 1.0f, 1.0f, 1.0f ) );
Parent::setTransform( MatrixF::Identity );
setGlobalBounds();
resetWorldBox();
addToScene();
if ( PHYSICSMGR )
{
PhysicsCollision *colShape = PHYSICSMGR->createCollision();
colShape->addPlane( PlaneF( Point3F::Zero, Point3F( 0, 0, 1 ) ) );
PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
mPhysicsRep = PHYSICSMGR->createBody();
mPhysicsRep->init( colShape, 0, 0, this, world );
}
return true;
}
void GroundPlane::onRemove()
{
if (!mMaterialAsset.isNull())
AssetDatabase.releaseAsset(mMaterialAsset.getAssetId());
//SAFE_DELETE(mMaterialInst);
SAFE_DELETE( mPhysicsRep );
removeFromScene();
Parent::onRemove();
}
void GroundPlane::inspectPostApply()
{
Parent::inspectPostApply();
setMaskBits( U32( -1 ) );
if( mSquareSize < sMIN_SQUARE_SIZE )
{
Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE );
mSquareSize = sMIN_SQUARE_SIZE;
}
setScale( VectorF( 1.0f, 1.0f, 1.0f ) );
}
void GroundPlane::setTransform( const MatrixF &mat )
{
// Ignore.
}
void GroundPlane::setScale( const Point3F& scale )
{
// Ignore.
}
U32 GroundPlane::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
stream->write( mSquareSize );
stream->write( mScaleU );
stream->write( mScaleV );
PACK_ASSET(connection, Material);
return retMask;
}
void GroundPlane::unpackUpdate( NetConnection* connection, BitStream* stream )
{
Parent::unpackUpdate( connection, stream );
stream->read( &mSquareSize );
stream->read( &mScaleU );
stream->read( &mScaleV );
UNPACK_ASSET(connection, Material);
// If we're added then something possibly changed in
// the editor... do an update of the material and the
// geometry.
if ( isProperlyAdded() )
{
_updateMaterial();
mVertexBuffer = NULL;
}
}
void GroundPlane::_updateMaterial()
{
if (mMaterialAsset.notNull())
{
if (mMaterialInst && String(mMaterialAsset->getMaterialDefinitionName()).equal(mMaterialInst->getMaterial()->getName(), String::NoCase))
return;
SAFE_DELETE(mMaterialInst);
mMaterialInst = MATMGR->createMatInstance(mMaterialAsset->getMaterialDefinitionName(), getGFXVertexFormat< VertexType >());
if (!mMaterialInst)
Con::errorf("GroundPlane::_updateMaterial - no Material called '%s'", mMaterialAsset->getMaterialDefinitionName());
}
}
bool GroundPlane::castRay( const Point3F& start, const Point3F& end, RayInfo* info )
{
PlaneF plane( Point3F( 0.0f, 0.0f, 0.0f ), Point3F( 0.0f, 0.0f, 1.0f ) );
F32 t = plane.intersect( start, end );
if( t >= 0.0 && t <= 1.0 )
{
info->t = t;
info->setContactPoint( start, end );
info->normal.set( 0, 0, 1 );
info->material = mMaterialInst;
info->object = this;
info->distance = 0;
info->faceDot = 0;
info->texCoord.set( 0, 0 );
return true;
}
return false;
}
void GroundPlane::buildConvex( const Box3F& box, Convex* convex )
{
mConvexList->collectGarbage();
Box3F planeBox = getPlaneBox();
if ( !box.isOverlapped( planeBox ) )
return;
// See if we already have a convex in the working set.
BoxConvex *boxConvex = NULL;
CollisionWorkingList &wl = convex->getWorkingList();
CollisionWorkingList *itr = wl.wLink.mNext;
for ( ; itr != &wl; itr = itr->wLink.mNext )
{
if ( itr->mConvex->getType() == BoxConvexType &&
itr->mConvex->getObject() == this )
{
boxConvex = (BoxConvex*)itr->mConvex;
break;
}
}
if ( !boxConvex )
{
boxConvex = new BoxConvex;
mConvexList->registerObject( boxConvex );
boxConvex->init( this );
convex->addToWorkingList( boxConvex );
}
// Update our convex to best match the queried box
if ( boxConvex )
{
Point3F queryCenter = box.getCenter();
boxConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, -GROUND_PLANE_BOX_HEIGHT_HALF );
boxConvex->mSize = Point3F( box.getExtents().x,
box.getExtents().y,
GROUND_PLANE_BOX_HEIGHT_HALF );
}
}
bool GroundPlane::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& )
{
polyList->setObject( this );
polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );
if(context == PLC_Navigation)
{
F32 z = getPosition().z;
Point3F
p0(box.minExtents.x, box.maxExtents.y, z),
p1(box.maxExtents.x, box.maxExtents.y, z),
p2(box.maxExtents.x, box.minExtents.y, z),
p3(box.minExtents.x, box.minExtents.y, z);
// Add vertices to poly list.
U32 v0 = polyList->addPoint(p0);
polyList->addPoint(p1);
polyList->addPoint(p2);
polyList->addPoint(p3);
// Add plane between first three vertices.
polyList->begin(0, 0);
polyList->vertex(v0);
polyList->vertex(v0+1);
polyList->vertex(v0+2);
polyList->plane(v0, v0+1, v0+2);
polyList->end();
// Add plane between last three vertices.
polyList->begin(0, 1);
polyList->vertex(v0+2);
polyList->vertex(v0+3);
polyList->vertex(v0);
polyList->plane(v0+2, v0+3, v0);
polyList->end();
return true;
}
Box3F planeBox = getPlaneBox();
polyList->addBox( planeBox, mMaterialInst );
return true;
}
void GroundPlane::prepRenderImage( SceneRenderState* state )
{
PROFILE_SCOPE( GroundPlane_prepRenderImage );
// TODO: Should we skip rendering the ground plane into
// the shadows? Its not like you can ever get under it.
if ( !mMaterial )
return;
// If we don't have a material instance after the override then
// we can skip rendering all together.
BaseMatInstance *matInst = state->getOverrideMaterial(mMaterialInst);
if ( !matInst )
return;
PROFILE_SCOPE( GroundPlane_prepRender );
// Update the geometry.
createGeometry( state->getCullingFrustum() );
if( mVertexBuffer.isNull() )
return;
#ifdef TORQUE_AFX_ENABLED
afxZodiacMgr::renderGroundPlaneZodiacs(state, this);
#endif
// Add a render instance.
RenderPassManager* pass = state->getRenderPass();
MeshRenderInst* ri = pass->allocInst< MeshRenderInst >();
ri->type = RenderPassManager::RIT_Mesh;
ri->vertBuff = &mVertexBuffer;
ri->primBuff = &mPrimitiveBuffer;
ri->prim = &mPrimitive;
ri->matInst = matInst;
ri->objectToWorld = pass->allocUniqueXform( MatrixF::Identity );
ri->worldToCamera = pass->allocSharedXform( RenderPassManager::View );
ri->projection = pass->allocSharedXform( RenderPassManager::Projection );
ri->visibility = 1.0f;
ri->translucentSort = matInst->getMaterial()->isTranslucent();
ri->defaultKey = matInst->getStateHint();
if( ri->translucentSort )
ri->type = RenderPassManager::RIT_Translucent;
// If we need lights then set them up.
if ( matInst->isForwardLit() )
{
LightQuery query;
query.init( getWorldSphere() );
query.getLights( ri->lights, 8 );
}
pass->addInst( ri );
}
/// Generate a subset of the ground plane matching the given frustum.
void GroundPlane::createGeometry( const Frustum& frustum )
{
PROFILE_SCOPE( GroundPlane_createGeometry );
enum { MAX_WIDTH = 256, MAX_HEIGHT = 256 };
// Project the frustum onto the XY grid.
Point2F min;
Point2F max;
projectFrustum( frustum, mSquareSize, min, max );
// Early out if the grid projection hasn't changed.
if( mVertexBuffer.isValid() &&
min == mMin &&
max == mMax )
return;
mMin = min;
mMax = max;
// Determine the grid extents and allocate the buffers.
// Adjust square size permanently if with the given frustum,
// we end up producing more than a certain limit of geometry.
// This is to prevent this code from causing trouble with
// long viewing distances.
// This only affects the client object, of course, and thus
// has no permanent effect.
U32 width = mCeil( ( max.x - min.x ) / mSquareSize );
if( width > MAX_WIDTH )
{
mSquareSize = mCeil( ( max.x - min.x ) / MAX_WIDTH );
width = MAX_WIDTH;
}
else if( !width )
width = 1;
U32 height = mCeil( ( max.y - min.y ) / mSquareSize );
if( height > MAX_HEIGHT )
{
mSquareSize = mCeil( ( max.y - min.y ) / MAX_HEIGHT );
height = MAX_HEIGHT;
}
else if( !height )
height = 1;
const U32 numVertices = ( width + 1 ) * ( height + 1 );
const U32 numTriangles = width * height * 2;
// Only reallocate if the buffers are too small.
if ( mVertexBuffer.isNull() || numVertices > mVertexBuffer->mNumVerts )
{
mVertexBuffer.set( GFX, numVertices, GFXBufferTypeDynamic );
}
if ( mPrimitiveBuffer.isNull() || numTriangles > mPrimitiveBuffer->mPrimitiveCount )
{
mPrimitiveBuffer.set( GFX, numTriangles*3, numTriangles, GFXBufferTypeDynamic );
}
// Generate the grid.
generateGrid( width, height, mSquareSize, min, max, mVertexBuffer, mPrimitiveBuffer );
// Set up GFX primitive.
mPrimitive.type = GFXTriangleList;
mPrimitive.numPrimitives = numTriangles;
mPrimitive.numVertices = numVertices;
}
/// Project the given frustum onto the ground plane and return the XY bounds in world space.
void GroundPlane::projectFrustum( const Frustum& frustum, F32 squareSize, Point2F& outMin, Point2F& outMax )
{
// Get the frustum's min and max XY coordinates.
const Box3F bounds = frustum.getBounds();
Point2F minPt( bounds.minExtents.x, bounds.minExtents.y );
Point2F maxPt( bounds.maxExtents.x, bounds.maxExtents.y );
// Round the min and max coordinates so they align on the grid.
minPt.x -= mFmod( minPt.x, squareSize );
minPt.y -= mFmod( minPt.y, squareSize );
F32 maxDeltaX = mFmod( maxPt.x, squareSize );
F32 maxDeltaY = mFmod( maxPt.y, squareSize );
if( maxDeltaX != 0.0f )
maxPt.x += ( squareSize - maxDeltaX );
if( maxDeltaY != 0.0f )
maxPt.y += ( squareSize - maxDeltaY );
// Add a safezone, so we don't touch the clipping planes.
minPt.x -= squareSize; minPt.y -= squareSize;
maxPt.x += squareSize; maxPt.y += squareSize;
outMin = minPt;
outMax = maxPt;
}
/// Generate a triangulated grid spanning the given bounds into the given buffers.
void GroundPlane::generateGrid( U32 width, U32 height, F32 squareSize,
const Point2F& min, const Point2F& max,
GFXVertexBufferHandle< VertexType >& outVertices,
GFXPrimitiveBufferHandle& outPrimitives )
{
// Generate the vertices.
VertexType* vertices = outVertices.lock();
for( F32 y = min.y; y <= max.y; y += squareSize )
for( F32 x = min.x; x <= max.x; x += squareSize )
{
vertices->point.x = x;
vertices->point.y = y;
vertices->point.z = 0.0;
vertices->texCoord.x = ( x / squareSize ) * mScaleU;
vertices->texCoord.y = ( y / squareSize ) * -mScaleV;
vertices->normal.x = 0.0f;
vertices->normal.y = 0.0f;
vertices->normal.z = 1.0f;
vertices->tangent.x = 1.0f;
vertices->tangent.y = 0.0f;
vertices->tangent.z = 0.0f;
vertices->binormal.x = 0.0f;
vertices->binormal.y = 1.0f;
vertices->binormal.z = 0.0f;
vertices++;
}
outVertices.unlock();
// Generate the indices.
U16* indices;
outPrimitives.lock( &indices );
U16 corner1 = 0;
U16 corner2 = 1;
U16 corner3 = width + 1;
U16 corner4 = width + 2;
for( U32 y = 0; y < height; ++ y )
{
for( U32 x = 0; x < width; ++ x )
{
indices[ 0 ] = corner3;
indices[ 1 ] = corner2;
indices[ 2 ] = corner1;
indices += 3;
indices[ 0 ] = corner3;
indices[ 1 ] = corner4;
indices[ 2 ] = corner2;
indices += 3;
corner1 ++;
corner2 ++;
corner3 ++;
corner4 ++;
}
corner1 ++;
corner2 ++;
corner3 ++;
corner4 ++;
}
outPrimitives.unlock();
}
void GroundPlane::getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList)
{
if (!mMaterialAsset.isNull() && mMaterialAsset->getAssetId() != MaterialAsset::smNoMaterialAssetFallback)
usedAssetsList->push_back_unique(mMaterialAsset->getAssetId());
}
DefineEngineMethod( GroundPlane, postApply, void, (),,
"Intended as a helper to developers and editor scripts.\n"
"Force trigger an inspectPostApply. This will transmit "
"material and other fields to client objects."
)
{
object->inspectPostApply();
}