Game Development Community

dev|Pro Game Development Curriculum

Invisible Wall

by Ronan · 03/25/2017 (5:06 pm) · 2 comments

Seeing the need for an invisible wall in T3D I made this adaptation based on PhysicalZone I hope it caters to all.

invisibleWall.h
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef _H_INVISIBLEWALL
#define _H_INVISIBLEWALL

#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _EARLYOUTPOLYLIST_H_
#include "collision/earlyOutPolyList.h"
#endif
#ifndef _MPOLYHEDRON_H_
#include "math/mPolyhedron.h"
#endif

class Convex;
class PhysicsBody;

class InvisibleWall : public SceneObject
{
   typedef SceneObject Parent;

   enum UpdateMasks {      
      ActiveMask        = Parent::NextFreeMask << 0,
      NextFreeMask      = Parent::NextFreeMask << 1
   };

  protected:

   static bool smRenderWall;

   // Basically ripped from trigger
   Polyhedron           mPolyhedron;
   EarlyOutPolyList     mClippedList;

   bool mActive;
   
   PhysicsBody *mPhysicsRep;
   Convex* mConvexList;
   void buildConvex(const Box3F& box, Convex* convex);

  public:
   InvisibleWall();
   ~InvisibleWall();

   // SimObject
   DECLARE_CONOBJECT(InvisibleWall);
   static void consoleInit();
   static void initPersistFields();
   bool onAdd();
   void onRemove();
   void inspectPostApply();

   // NetObject
   U32  packUpdate  (NetConnection *conn, U32 mask, BitStream *stream);
   void unpackUpdate(NetConnection *conn,           BitStream *stream);

   // SceneObject
   void setTransform(const MatrixF &&mat);
   void prepRenderImage( SceneRenderState* state );
   
   void setPolyhedron(const Polyhedron&);
   bool testObject(SceneObject*);

   bool testBox( const Box3F &box ) const;

   void renderObject( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );

   void activate();
   void deactivate();
   inline bool isActive() const { return mActive; }

   bool isInvisibleWall(SceneObject* who);
   PhysicsBody* getPhysicsRep();
};

#endif // _H_INVISIBLEWALL

invisibleWall.cpp
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "T3D/invisibleWall.h"
#include "core/stream/bitStream.h"
#include "collision/boxConvex.h"
#include "collision/clippedPolyList.h"
#include "console/consoleTypes.h"
#include "math/mathIO.h"
#include "scene/sceneRenderState.h"
#include "T3D/trigger.h"
#include "gfx/gfxTransformSaver.h"
#include "renderInstance/renderPassManager.h"
#include "gfx/gfxDrawUtil.h"
#include "console/engineAPI.h"
#include "physics/physicsPlugin.h"
#include "physics/physicsBody.h"
#include "physics/physicsCollision.h"

IMPLEMENT_CO_NETOBJECT_V1(InvisibleWall);


bool InvisibleWall::smRenderWall = true;


DefineEngineMethod(InvisibleWall, activate, void, (),, "Activate the wall.n")
{
   if (object->isClientObject())
      return;

   object->activate();
}

DefineEngineMethod(InvisibleWall, deactivate, void, (),, "Deactivate the wall.n" )
{
   if (object->isClientObject())
      return;

   object->deactivate();
}


//--------------------------------------------------------------------------
//--------------------------------------
//
InvisibleWall::InvisibleWall()
{
   mNetFlags.set(Ghostable | ScopeAlways);

   mTypeMask |= InvisibleWallObjectType;


   mConvexList = new Convex;
   mActive = true;
   
   mPhysicsRep = NULL;
}

InvisibleWall::~InvisibleWall()
{
   delete mConvexList;
   mConvexList = NULL;
}

//--------------------------------------------------------------------------
void InvisibleWall::consoleInit()
{
	Con::addVariable( "$InvisibleWall::renderWall", TypeBool, &&smRenderWall, "If true, a box will render around the location of all walls.n");
}

void InvisibleWall::initPersistFields()
{
   addGroup("Misc");
   addField("polyhedron",   TypeTriggerPolyhedron, Offset(mPolyhedron,   InvisibleWall),
      "The polyhedron type is really a quadrilateral and consists of a corner"
      "point followed by three vectors representing the edges extending from the corner." );
   endGroup("Misc");
   Parent::initPersistFields();
}

//--------------------------------------------------------------------------
bool InvisibleWall::onAdd()
{
   if(!Parent::onAdd())
      return false;

   Polyhedron temp = mPolyhedron;
   setPolyhedron(temp);

   addToScene();

   if ( PHYSICSMGR/* && isServerObject() */)
   {
	   PhysicsCollision *colShape = PHYSICSMGR->createCollision();
	   Box3F scaledBox = mObjBox;
	   scaledBox.minExtents.convolve(mObjScale);
	   scaledBox.maxExtents.convolve(mObjScale);
	   colShape->addBox( scaledBox.getExtents() * 0.5f, MatrixF::Identity );

	   PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
	   mPhysicsRep = PHYSICSMGR->createBody();
	   mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
	   MatrixF tr = getTransform();
	   MatrixF localTr(true);
	   localTr.setPosition(scaledBox.getCenter());
	   tr = tr*localTr;
	   mPhysicsRep->setTransform(tr);
   }

   return true;
}


void InvisibleWall::onRemove()
{
   SAFE_DELETE(mPhysicsRep);
   mConvexList->nukeList();

   removeFromScene();
   Parent::onRemove();
}

PhysicsBody* InvisibleWall::getPhysicsRep()
{
	return mPhysicsRep;
}

void InvisibleWall::inspectPostApply()
{
   setPolyhedron(mPolyhedron);
   Parent::inspectPostApply();
}

//------------------------------------------------------------------------------
void InvisibleWall::setTransform(const MatrixF && mat)
{
   Parent::setTransform(mat);

      MatrixF base(true);
      base.scale(Point3F(1.0/mObjScale.x,
                         1.0/mObjScale.y,
                         1.0/mObjScale.z));
      base.mul(mWorldToObj);
      mClippedList.setBaseTransform(base);

   if (isServerObject())
      setMaskBits(InitialUpdateMask);
}

extern bool gEditorEnabled;

void InvisibleWall::prepRenderImage( SceneRenderState *state )
{
   // only render if selected or render flag is set
   if (!gEditorEnabled || !smRenderWall )
      return;

   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
   ri->renderDelegate.bind( this, &&InvisibleWall::renderObject );
   ri->type = RenderPassManager::RIT_Editor;
   ri->defaultKey = 0;
   ri->defaultKey2 = 0;
   state->getRenderPass()->addInst( ri );
}


void InvisibleWall::renderObject( ObjectRenderInst *ri,
                                 SceneRenderState *state,
                                 BaseMatInstance *overrideMat )
{
   if (overrideMat)
      return;

   GFXStateBlockDesc desc;
   desc.setZReadWrite( true, false );
   desc.setBlend( true );
   desc.setCullMode( GFXCullNone );

   GFXTransformSaver saver;

   MatrixF mat = getRenderTransform();
   mat.scale( getScale() );

   GFX->multWorld( mat );

   GFXDrawUtil *drawer = GFX->getDrawUtil();
   drawer->drawPolyhedron( desc, mPolyhedron, ColorI( 255, 0, 0, 45 ) );

   desc.setFillModeWireframe();
   drawer->drawPolyhedron( desc, mPolyhedron, ColorF::BLACK );
}

//--------------------------------------------------------------------------
U32 InvisibleWall::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
   U32 i;
   U32 retMask = Parent::packUpdate(con, mask, stream);

   if (stream->writeFlag((mask && InitialUpdateMask) != 0)) {
      // Note that we don't really care about efficiency here, since this is an
      //  edit-only ghost...
      mathWrite(*stream, mObjToWorld);
      mathWrite(*stream, mObjScale);

      // Write the polyhedron
      stream->write(mPolyhedron.pointList.size());
      for (i = 0; i < mPolyhedron.pointList.size(); i++)
         mathWrite(*stream, mPolyhedron.pointList[i]);

      stream->write(mPolyhedron.planeList.size());
      for (i = 0; i < mPolyhedron.planeList.size(); i++)
         mathWrite(*stream, mPolyhedron.planeList[i]);

      stream->write(mPolyhedron.edgeList.size());
      for (i = 0; i < mPolyhedron.edgeList.size(); i++) {
         const Polyhedron::Edge& rEdge = mPolyhedron.edgeList[i];

         stream->write(rEdge.face[0]);
         stream->write(rEdge.face[1]);
         stream->write(rEdge.vertex[0]);
         stream->write(rEdge.vertex[1]);
      }

      stream->writeFlag(mActive);
   } else {
      stream->writeFlag(mActive);
   }

   return retMask;
}

void InvisibleWall::unpackUpdate(NetConnection* con, BitStream* stream)
{
   Parent::unpackUpdate(con, stream);

   if (stream->readFlag()) {
      U32 i, size;
      MatrixF temp;
      Point3F tempScale;
      Polyhedron tempPH;

      // Transform
      mathRead(*stream, &temp);
      mathRead(*stream, &tempScale);

      // Read the polyhedron
      stream->read(&&size);
      tempPH.pointList.setSize(size);
      for (i = 0; i < tempPH.pointList.size(); i++)
         mathRead(*stream, &tempPH.pointList[i]);

      stream->read(&&size);
      tempPH.planeList.setSize(size);
      for (i = 0; i < tempPH.planeList.size(); i++)
         mathRead(*stream, &tempPH.planeList[i]);

      stream->read(&&size);
      tempPH.edgeList.setSize(size);
      for (i = 0; i < tempPH.edgeList.size(); i++) {
         Polyhedron::Edge& rEdge = tempPH.edgeList[i];

         stream->read(&&rEdge.face[0]);
         stream->read(&&rEdge.face[1]);
         stream->read(&&rEdge.vertex[0]);
         stream->read(&&rEdge.vertex[1]);
      }

      setPolyhedron(tempPH);
      setScale(tempScale);
      setTransform(temp);
      mActive = stream->readFlag();
   } else {
      mActive = stream->readFlag();
   }
}
//--------------------------------------------------------------------------
void InvisibleWall::setPolyhedron(const Polyhedron& rPolyhedron)
{
   mPolyhedron = rPolyhedron;

   if (mPolyhedron.pointList.size() != 0) {
      mObjBox.minExtents.set(1e10, 1e10, 1e10);
      mObjBox.maxExtents.set(-1e10, -1e10, -1e10);
      for (U32 i = 0; i < mPolyhedron.pointList.size(); i++) {
         mObjBox.minExtents.setMin(mPolyhedron.pointList[i]);
         mObjBox.maxExtents.setMax(mPolyhedron.pointList[i]);
      }
   } else {
      mObjBox.minExtents.set(-0.5, -0.5, -0.5);
      mObjBox.maxExtents.set( 0.5,  0.5,  0.5);
   }

   MatrixF xform = getTransform();
   setTransform(xform);

   mClippedList.clear();
   mClippedList.mPlaneList = mPolyhedron.planeList;

   MatrixF base(true);
   base.scale(Point3F(1.0/mObjScale.x,
                      1.0/mObjScale.y,
                      1.0/mObjScale.z));
   base.mul(mWorldToObj);

   mClippedList.setBaseTransform(base);
}
//--------------------------------------------------------------------------
void InvisibleWall::buildConvex(const Box3F& box, Convex* convex)
{
   // These should really come out of a pool
   mConvexList->collectGarbage();

   Box3F realBox = box;
   mWorldToObj.mul(realBox);
   realBox.minExtents.convolveInverse(mObjScale);
   realBox.maxExtents.convolveInverse(mObjScale);

   if (realBox.isOverlapped(getObjBox()) == false)
      return;

   // Just return a box convex for the entire shape...
   Convex* cc = 0;
   CollisionWorkingList& wl = convex->getWorkingList();
   for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
      if (itr->mConvex->getType() == BoxConvexType &&
          itr->mConvex->getObject() == this) {
         cc = itr->mConvex;
         break;
      }
   }
   if (cc)
      return;

   // Create a new convex.
   BoxConvex* cp = new BoxConvex;
   mConvexList->registerObject(cp);
   convex->addToWorkingList(cp);
   cp->init(this);

   mObjBox.getCenter(&&cp->mCenter);
   cp->mSize.x = mObjBox.len_x() / 2.0f;
   cp->mSize.y = mObjBox.len_y() / 2.0f;
   cp->mSize.z = mObjBox.len_z() / 2.0f;
}

bool InvisibleWall::testObject(SceneObject* enter)
{
   // TODO: This doesn't look like it's testing against the polyhedron at
   // all.  And whats the point of building a convex if no collision methods
   // are implemented?

   if (mPolyhedron.pointList.size() == 0)
      return false;

   mClippedList.clear();

   SphereF sphere;
   sphere.center = (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5;
   VectorF bv = mWorldBox.maxExtents - sphere.center;
   sphere.radius = bv.len();

   enter->buildPolyList(PLC_Collision, &&mClippedList, mWorldBox, sphere);
   return mClippedList.isEmpty() == false;
}

bool InvisibleWall::testBox( const Box3F &&box ) const
{
   return mWorldBox.isOverlapped( box );
}

bool InvisibleWall::isInvisibleWall(SceneObject* who)
{
	bool b = false;
	if(who)
	{
		b = true;
		const char* result = Con::executef("isWallForObject", this->getIdString(), who->getIdString());
		if(dStricmp(result, "") > 0)
			b = dAtob(result);
		return b;
	}
	
	return false;
}

void InvisibleWall::activate()
{
   if (mActive != true)
      setMaskBits(ActiveMask);
   mActive = true;
}

void InvisibleWall::deactivate()
{
   if (mActive != false)
      setMaskBits(ActiveMask);
   mActive = false;
}
in T3D/objectTypes.h add at bottom
InvisibleWallObjectType =                BIT(26),

in file T3D/player.cpp

change
static U32 sCollisionMoveMask =  TerrainObjectType       |
                                 WaterObjectType         | 
                                 PlayerObjectType        |
                                 StaticShapeObjectType   | 
                                 VehicleObjectType       |
                                 PhysicalZoneObjectType;

to
static U32 sCollisionMoveMask =  TerrainObjectType       |
                                 WaterObjectType         | 
                                 PlayerObjectType        |
                                 StaticShapeObjectType   | 
                                 VehicleObjectType       |
								 InvisibleWallObjectType |
                                 PhysicalZoneObjectType;


find function
Point3F Player::_move( const F32 travelTime, Collision *outCol )

replace

if (plistBox.isOverlapped(convexBox))
            {
               if (pConvex->getObject()->getTypeMask() & PhysicalZoneObjectType)
                  pConvex->getPolyList(&&sPhysZonePolyList);
               else
                  pConvex->getPolyList(&&sExtrudedPolyList);
            }

with

if (plistBox.isOverlapped(convexBox))
            {	
				if (pConvex->getObject()->getTypeMask() & InvisibleWallObjectType)
				{
					InvisibleWall* pWall = (InvisibleWall*)pConvex->getObject();
					bool b = pWall->isInvisibleWall(this);
					if (pWall->isActive() &&&& b)
                       pConvex->getPolyList(&&sExtrudedPolyList);
				}
				else
                if (pConvex->getObject()->getTypeMask() & PhysicalZoneObjectType)
				{
					pConvex->getPolyList(&&sPhysZonePolyList);
				}
               else
                  pConvex->getPolyList(&&sExtrudedPolyList);
            }

in T3d/physics/bt/btPlayer.cpp replace class BtPlayerSweepCallback with this

class BtPlayerSweepCallback : public btCollisionWorld::ClosestConvexResultCallback
{
	typedef btCollisionWorld::ClosestConvexResultCallback Parent;

public:

	BtPlayerSweepCallback( btCollisionObject *me, const btVector3 &moveVec  ) 
		:  Parent( btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0) ),
		mMe( me ),
		mMoveVec( moveVec )
	{
	}

	virtual bool needsCollision(btBroadphaseProxy* proxy0) const
	{
		if ( proxy0->m_clientObject == mMe )
			return false;

		// behavior of invisible wall
		btCollisionObject* colObj = (btCollisionObject*)(proxy0->m_clientObject);
		SceneObject* sceneObj = PhysicsUserData::getObject(colObj->getUserPointer());
		if (sceneObj && (sceneObj->getTypeMask() & InvisibleWallObjectType))
		{
			SceneObject* playerObject = PhysicsUserData::getObject(mMe->getUserPointer());
			InvisibleWall* pZone = static_cast<InvisibleWall*>(sceneObj);
			if (pZone->isActive() && pZone->isInvisibleWall(playerObject))
				return true;
		}

		if ( !colObj->hasContactResponse() )
			return false;

		return Parent::needsCollision( proxy0 );
	}

	virtual btScalar addSingleResult(   btCollisionWorld::LocalConvexResult &convexResult, 
		bool normalInWorldSpace )
	{
		// NOTE: I shouldn't have to do any of this, but Bullet 
		// has some weird bugs.
		//
		// For one the plane type will return hits on a Z up surface
		// for sweeps that have no Z sweep component.
		//
		// Second the normal returned here is sometimes backwards
		// to the sweep direction... no clue why.
		//
		F32 dotN = mMoveVec.dot( convexResult.m_hitNormalLocal );
		if ( mFabs( dotN ) < 0.1f )
			return 1.0f;

		return Parent::addSingleResult( convexResult, normalInWorldSpace );
	}

protected:
	btVector3 mMoveVec;
	btCollisionObject *mMe;
};

to add in world editor

in tools/worldeditor/scripts/editors/creator.ed.cs add in level group

%this.registerMissionObject( "InvisibleWall", "Invisible Wall" );

in tools/worldeditor/gui/objectBuilderGui.ed.gui add

function ObjectBuilderGui::buildInvisibleWall(%this)
{
   %this.objectClassName = "InvisibleWall";

   %this.addField("polyhedron", "TypeTriggerPolyhedron", "Polyhedron", "-0.5 0.5 0.0 1.0 0.0 0.0 0.0 -1.0 0.0 0.0 0.0 1.0");

   %this.process();
}

Use this function to toggle wall transposition using variable

$IgnoreWall = false;
function isWallForObject(%wall, %obj)
{
   return !$IgnoreWall;
}

$IgnoreWall = true; // Player can cross the wall
$IgnoreWall = false; // Player can not cross wall

To prevent errors, the wall depth should be as large as possible!

I hope I have helped :)

About the author

Recent Blogs

• Script IDE

#1
04/16/2017 (7:52 pm)
I like this concept. I hope to test it out and use it soon. Thank you for sharing this.
#2
06/08/2017 (4:22 pm)
Nice, I will try it when I get the time, so far I used a physical zone with a push force applied pointing inside the level so players cannot go outside.