Game Development Community

Add Radial Force and Vorticity to PhysicalZone

by Orion Elenzil · 05/23/2007 (10:13 pm) · 5 comments

See source code comments below for description.

Modifications based on TGE 1.3.5:
Basic familiarity with the TGE engine and C++ is assumed.

PhysicalZone.h
Add the following comments up at the top:
//-----------------------------------------------------------------------------
// Radial Force in Physical Zones.
// Orion Elenzil
// Doppelganger 200705
// 
// This resource provides two new forces to PhysicalZones,
// in addition to the beloved AppliedForce, VelocityMod, and GravityMod.
// 
// The first new force is Radial, in that it operates along the line
// between the player and the center of the physical zone,
// or optionally the center Z-Axis of the zone.
// 
// The second is vorticity, which pushes the player in a circle
// tangentially to the Z-axis.
// 
// The best way to picture these forces is as a hollow cylinder centered on the zone,
// with one force at the inner wall, another force at the outer wall,
// and interpolated forces in between.
// 
// One of the simplest uses of this is to create a region which is very difficult to walk to.
// Say a magic altar. To achieve something like that, the inner wall of the cylinder would
// have radius zero and a strong force pushing outward. The outer wall would have radius
// of say five, and say zero force.  When the player approaches the altar, they find that
// it gets more and more difficult to walk towards it.
// 
// Another use could be similar to a black hole.
// To do this, the inner radius should be something small but positive like 0.5,
// and have a strong inward force, and the outer radius again has relatively small force.
// 
// Other more esoteric possibilities exist as well.
// 
// For example, one could create a hollow force-ring,
// where people inside can walk around but can't get out,
// and people outside can't get in.
// 
// The inverse of that is attractive,
// where you create a ring which sucks people to its center,
// and then can walk around the ring but can't leave it.
//
// A vertical twister is easily achieved by combining a vertical VelocityMod
// with both radial attraction and vorticity.
// 
// There is no visualization of the Radial Forces in the mission editor, sorry.
// 
// 
// 
// Usage
// -----
// The radius & force associateed with inner & outer walls are specified as three-component points: [b]radialForcePt1[/b] and [b]radialForcePt2[/b].
// radialForcePt1.x is the radius    at the inner wall
// radialForcePt1.y is the force     at the inner wall
// radialForcePt1.z is the vorticity at the inner wall
// radialForcePt2.x is the radius    at the outer wall
// radialForcePt2.y is the force     at the outer wall
// radialForcePt2.y is the vorticity at the outer wall
// 
// in a mission file for the black hole example, these might look like:
// new PhysicalZone(phzzy) {
//    usual stuff ..
//    radialForcePt1 = "0.5 -5000 3000";
//    radialForcePt2 = "5 0 0";
// };
// 
// 
// another parameter which can be supplied is [b]radialForceCylindrical[/b].
// this parameter is a boolean and is default to [b]true[/b].
// if set to [b]false[/b], this parameter causes the radial force to act as spherical shells
// instead of cylindrical shells.  i haven't experimented much/at all with this mode.
// 
// vorticity is only applied when radialForceCylindrical is true.
//-----------------------------------------------------------------------------

find the line "Point3F mAppliedForce;",
and put these lines after it:
Point3F    mRadialForcePt1;
   Point3F    mRadialForcePt2;
   bool       mRadialForceCylindrical;

find the line "const Point3F& getForce() const { return mAppliedForce; }",
and put these lines after it:
VectorF getRadialForce         (const Point3F& worldPoint);
   F32     getRadialForceMagnitude(F32 distance             );
   F32     getRadialForceOrbit    (F32 distance             );



PhysicalZone.cc
add the following lines to the constructor:
mRadialForcePt1.set(1.0f, 0.0f, 0.0f);
   mRadialForcePt2.set(3.0f, 0.0f, 0.0f);
   mRadialForceCylindrical = true;

add the following lines at the bottom of PhysicalZone::initPersistFields():
addGroup("Radial Force");
   addField("radialForcePt1"         , TypePoint3F          , Offset(mRadialForcePt1        , PhysicalZone));
   addField("radialForcePt2"         , TypePoint3F          , Offset(mRadialForcePt2        , PhysicalZone));
   addField("radialForceCylindrical" , TypeBool             , Offset(mRadialForceCylindrical, PhysicalZone));
   endGroup("Radial Force");


in PhysicalZone::onAdd(),
find the line "setPolyhedron(temp)",
and add these lines after it (before addToScene()):
//---------------------------------------------------------
   // Radial Force Sanity Checks
   if (mRadialForcePt1.x < 0)
   {
      Con::errorf("PhysicalZone: radialForcePt1 negative. Swapping.");
      mRadialForcePt1.x *= -1.0f;
   }

   if (mRadialForcePt2.x < 0)
   {
      Con::errorf("PhysicalZone: radialForcePt2 negative. Swapping.");
      mRadialForcePt2.x *= -1.0f;
   }

   if (mRadialForcePt1.x > mRadialForcePt2.x)
   {
      Con::errorf("PhysicalZone: radialForcePts out of order. Reordering");
      Point3F tmp     = mRadialForcePt1;
      mRadialForcePt1 = mRadialForcePt2;
      mRadialForcePt2 = tmp;
   }

   if (mRadialForcePt2.x - mRadialForcePt1.x < 0.0001f)
   {
      Con::errorf("PhysicalZone: radialForcePoints too close. Moving.");
      mRadialForcePt2.x = mRadialForcePt1.x + 0.0002f;
   }
   //---------------------------------------------------------


in PhysicalZone::PackUpdate(),
find the line "stream->writeFlag(mActive);",
and add the following lines after it:
// RadialForce
      mathWrite(*stream, mRadialForcePt1);
      mathWrite(*stream, mRadialForcePt2);
      stream->write(mRadialForceCylindrical);

in PhysicalZone::unpackUpdate(),
find the line "mActive = stream->readFlag();",
and add the following lines after it:
// RadialForce
      mathRead(*stream, &mRadialForcePt1);
      mathRead(*stream, &mRadialForcePt2);
      stream->read(&mRadialForceCylindrical);


add the following lines at the bottom of the file:
//--------------------------------------
VectorF PhysicalZone::getRadialForce(const Point3F& worldPoint)
{
   Point3F pos;
   getWorldBox().getCenter(&pos);
   VectorF v = worldPoint - pos;
   if (mRadialForceCylindrical)
      v.z = 0.0f;

   F32     vLen  = v.len();
   VectorF vNorm = v * 1.0f / vLen;

   F32 forceMagnitude = getRadialForceMagnitude(vLen);
   VectorF fm = vNorm * forceMagnitude;

   VectorF fo(0.0f, 0.0f, 0.0f);

   if (mRadialForceCylindrical)
   {
      F32 forceOrbit = getRadialForceOrbit(vLen);
      VectorF vo = mCross(VectorF(0.0f, 0.0f, 1.0f), vNorm);
      fo = vo * forceOrbit;
   }

   return fm + fo;
}


F32 PhysicalZone::getRadialForceMagnitude(F32 distance    )
{
   if (distance < mRadialForcePt1.x)
      return 0.0f;
   if (distance > mRadialForcePt2.x)
      return 0.0f;

   F32 radiusRange    = mRadialForcePt2.x - mRadialForcePt1.x;
   F32 forceRange     = mRadialForcePt2.y - mRadialForcePt1.y;
   AssertFatal((radiusRange < -0.00001f) || (radiusRange > 0.00001f), "radiusRange degenerate");
   F32 distNormalized = (distance - mRadialForcePt1.x) / radiusRange;

   // Good old linear interpolation.
   // Something like cosine interpolation might be smoother/better, but linear is probably okay
   F32 force = distNormalized * forceRange + mRadialForcePt1.y;

   return force;
}

F32 PhysicalZone::getRadialForceOrbit(F32 distance    )
{
   if (distance < mRadialForcePt1.x)
      return 0.0f;
   if (distance > mRadialForcePt2.x)
      return 0.0f;

   F32 radiusRange    = mRadialForcePt2.x - mRadialForcePt1.x;
   F32 forceRange     = mRadialForcePt2.z - mRadialForcePt1.z;
   AssertFatal((radiusRange < -0.00001f) || (radiusRange > 0.00001f), "radiusRange degenerate");
   F32 distNormalized = (distance - mRadialForcePt1.x) / radiusRange;

   // Good old linear interpolation.
   // Something like cosine interpolation might be smoother/better, but linear is probably okay
   F32 force = distNormalized * forceRange + mRadialForcePt1.z;

   return force;
}



ShapeBase.cc
in physicalZoneFind(),
find the line "shape->mAppliedForce += pz->getForce();",
and after it add the following line:
shape->mAppliedForce += pz->getRadialForce(shape->getPosition());


.. and that's it !

#1
05/24/2007 (12:46 am)
this one is really interesting! While reading description I came up with a couple of ideas on how to use it :)
huge thanks for sharing this with the community, Orion!
#2
05/24/2007 (8:15 am)
sounds interesting! Do you have a video of that effect in action? :)
#3
05/24/2007 (11:41 am)
This is going to fit wonderfully into my space game.... Thanx!!!!
#4
05/25/2007 (9:06 am)
Very inspiring ressource.
#5
09/21/2007 (2:42 pm)
That's brilliant! Force-fields made easy, and I'm thinking about some cool grenade types...