Game Development Community

[bug 1.1a] Zones and Portals bugs - RESOLVED

by Marcus L · in Torque 3D Professional · 01/30/2010 (11:29 am) · 161 replies

I'm having issues with portals and zones. I searched for the fix but both this and this is already applied in alpha 1.1. My issue is that when I'm inside a portal thats linked to a zone, then if i look in a certain direction (usually a random direction), the content of the zone is unrendered (btw. i think this is the same issue as Joshua Halls is/was having trouble with).

To make it easier to understand my problem I'll post some pictures.

Here i am inside a portal, liked to a zone.
img715.imageshack.us/img715/5185/screenshot00100000.png
Then i turn my head slightly to the left, and...
img715.imageshack.us/img715/1531/screenshot00100003.pngIt's gone.

If anyone knows the fix, could u please post it here.
Thanks.
#21
02/02/2011 (5:49 pm)
this wont fix the way some things mostly projectiles dissapear at certain angles will it?

would love to be able to change the light in zones
#22
02/02/2011 (6:53 pm)
Quote:this wont fix the way some things mostly projectiles dissapear at certain angles will it?

If they're flying through zones, then yes, the bug here will make them disappear and reappear depending on viewing angle. All SceneObjects are affected by this.
#23
02/03/2011 (5:57 am)
Culling and altering the ambient/sun per zone has been on my short list of improvements for a long time now. In BL it will suck more as you cannot easily do per-pixel ambient, but it would be much better than what we have now.

I'll see what I can do to make this happen, but I'm not sure of the timing. At least Rene would have fixed the zoning by then. ;)
#24
02/03/2011 (6:50 am)
@Rene - it would probably be ok if adjacent zones were set to the same ambient level. From an artist perspective I guess gradually darkening the scene as you progress deeper into a cave by using multiple zones to lower ambient light would be kinda cool - especially if it won't affect other clients. Agreed it is kludgy, but if it doesn't affect the server state then it's an acceptable hack in my book until Tom comes up with something better....

Just sayin - you guys rock.
#25
02/04/2011 (8:25 am)

Ok, think I finally nailed this one. As suspected, the frustum culling problems introduced by Portal are the source of a lot of zoning issues. I'll add the changes in subsequent posts (character limit...) so you can try the fix. So far, everything looks stable and good.

Quote:Just sayin - you guys rock.

Thanks :)
#26
02/04/2011 (8:25 am)
Replace Portal::generatePortalFrustum with

bool Portal::generatePortalFrustum( SceneState* state, Frustum *outFrustum )
{
   const Frustum& frustum = state->getFrustum();

   const RectI& viewport = GFX->getViewport();
   const MatrixF& worldMat = GFX->getWorldMatrix();
   const MatrixF& projMat = GFX->getProjectionMatrix();

   // To accommodate how the Frustum stuff is set up in Torque, we need to jump
   // through a couple of hoops here.  Normally, we could just clip the portal
   // polygon against the frustum's planes and then set up a new clipping plane
   // for each of the edges in the resulting polygon.

   // First, we need to clip the portal's rectangle against the current
   // view frustum.  Since clipping a rectangle against a plane may produce
   // a pentagon and since we are clipping against all six planes of a frustum,
   // we may end up with a polygon of at most 10 vertices.
   //
   // Use two buffers here in interchanging roles as sources and targets
   // in clipping against the frustum planes.

   Point3F polygonBuffer1[ 10 ];
   Point3F polygonBuffer2[ 10 ];

   Point3F* tempPolygon = polygonBuffer1;
   Point3F* resultPolygon = polygonBuffer2;

   dMemcpy( resultPolygon, mOrientedPortalPoints, sizeof( mOrientedPortalPoints ) );

   U32 numResultPolygonVertices = 4;
   U32 numTempPolygonVertices = 0;

   for( U32 nplane = 0; nplane < Frustum::PlaneCount; ++ nplane )
   {
      // Make the output of the last iteration the
      // input of this iteration.

      swap( tempPolygon, resultPolygon );
      numTempPolygonVertices = numResultPolygonVertices;

      // Clip our current remainder of the original portal rectangle
      // against the current plane.

      const PlaneF& plane = frustum.getPlanes()[ nplane ];
      numResultPolygonVertices = plane.clipPolygon( tempPolygon, numTempPolygonVertices, resultPolygon );

      // If the polygon was completely on the backside of the plane,
      // then the portal is outside the frustum.  In this case, set the output
      // frustum to the input frustum and return false to indicate we haven't
      // clipped anything.

      if( !numResultPolygonVertices )
      {
         *outFrustum = frustum;
         return false;
      }
   }

   // Project the clipped polygon into screen space.

   Point3F projectedPolygon[ 10 ];
   for( U32 i = 0; i < numResultPolygonVertices; ++ i )
      MathUtils::mProjectWorldToScreen( resultPolygon[ i ], &projectedPolygon[ i ], viewport, worldMat, projMat );

   // Put an AABB around our polygon.

   Box3F aabb = Box3F::aroundPoints( projectedPolygon, numResultPolygonVertices );

   // Just to be safe, clamp our screens-space coordinates to the viewport.
   // Since we already have clipped everything to the frustum above, we don't
   // strictly need to do that, but it doesn't hurt.

   Point2F clampedMin;
   Point2F clampedMax;

   clampedMin.x = mClampF( aabb.minExtents.x, ( F32 ) viewport.point.x, ( F32 ) viewport.point.x + viewport.extent.x );
   clampedMin.y = mClampF( aabb.minExtents.y, ( F32 ) viewport.point.y, ( F32 ) viewport.point.y + viewport.extent.y );

   clampedMax.x = mClampF( aabb.maxExtents.x, ( F32 ) viewport.point.x, ( F32 ) viewport.point.x + viewport.extent.x );
   clampedMax.y = mClampF( aabb.maxExtents.y, ( F32 ) viewport.point.y, ( F32 ) viewport.point.y + viewport.extent.y );

   // Get the extents of the current frustum.

   F32 frustumXExtent = mFabs( frustum.getNearRight() - frustum.getNearLeft() );
   F32 frustumYExtent = mFabs( frustum.getNearTop() - frustum.getNearBottom() );

   // Now, normalize the screen-space pixel coordinates to lie within the screen-centered
   // -1 to 1 coordinate space that is used for the frustum planes.

   Point2F normalizedMin;
   Point2F normalizedMax;

   normalizedMin.x = ( ( clampedMin.x / viewport.extent.x ) * frustumXExtent ) - ( frustumXExtent / 2.f );
   normalizedMin.y = ( ( clampedMin.y / viewport.extent.y ) * frustumYExtent ) - ( frustumYExtent / 2.f );
   normalizedMax.x = ( ( clampedMax.x / viewport.extent.x ) * frustumXExtent ) - ( frustumXExtent / 2.f );
   normalizedMax.y = ( ( clampedMax.y / viewport.extent.y ) * frustumYExtent ) - ( frustumYExtent / 2.f );

   // Finally, create the new frustum using the original's frustum
   // information except its left/right/top/bottom planes.

   outFrustum->set(
      frustum.isOrtho(),
      normalizedMin.x,
      normalizedMax.x,
      - normalizedMin.y,
      - normalizedMax.y,
      frustum.getNearDist(),
      frustum.getFarDist(),
      frustum.getTransform()
   );

   return true;
}
#27
02/04/2011 (8:26 am)
Add this method to PlaneF:

U32 PlaneF::clipPolygon( Point3F* inVertices, U32 inNumVertices, Point3F* outVertices ) const
{
   AssertFatal( inVertices != NULL, "PlaneF::clipPolygon - Received NULL inVertices" );
   AssertFatal( outVertices != NULL, "PlaneF::clipPolygon - Received NULL outVertices" );

   // We silently accept two-point "polygons" since they will be correctly clipped.
   AssertFatal( inNumVertices >= 2, "PlaneF::clipPolygon - Must have at least two vertices" );

   AssertFatal( MathUtils::isPlanarPolygon( inVertices, inNumVertices ), "PlaneF::clipPolygon - Polygon must be planar" );

   U32 numResultVertices = 0;
   for( U32 i = 0; i < inNumVertices; ++ i )
   {
      const Point3F& vertex1 = inVertices[ i ];
      const Point3F& vertex2 = inVertices[ ( i + 1 ) % inNumVertices ];

      // Find sides of each vertex to determine whether
      // the edge connecting them goes through the plane.

      Side side1 = whichSide( vertex1 );
      Side side2 = whichSide( vertex2 );

      // Skip edges that are fully behind the plane.

      if( side1 == Back && side2 == Back )
         continue;

      // If the first vertex is in front of the plane,
      // we definitely add it.

      if( side1 == Front )
      {
         outVertices[ numResultVertices ] = vertex1;
         numResultVertices ++;
      }

      // For edges fully in front or on the plane,
      // that's all we need to do.

      if( ( side1 == Front || side1 == On ) &&
          ( side2 == Front || side2 == On ) )
         continue;

      // Split edges that intersect the plane.

      const Point3F* from;
      const Point3F* to;

      if( side1 == Front )
      {
         from = &vertex1;
         to = &vertex2;
      }
      else
      {
         from = &vertex2;
         to = &vertex1;
      }

      F32 dt = intersect( *from, *to );
      outVertices[ numResultVertices ] = *from + ( *to - *from ) * dt;
      numResultVertices ++;

      AssertFatal( numResultVertices <= inNumVertices + 1, "PlaneF::clipPolygon - Generated excessive vertices; input polygon convex?" );
   }

   AssertFatal( numResultVertices != 1, "PlaneF::clipPolygon - Generated invalid polygon" );
   AssertFatal( !numResultVertices || MathUtils::isPlanarPolygon( outVertices, numResultVertices ), "PlaneF::clipPolygon - Computed polygon isn't planar" );
   
   return numResultVertices;
}
#28
02/04/2011 (8:27 am)
Either take the respective AssertFatals out of the previous method or add this function to the MathUtils namespace:

bool isPlanarPolygon( const Point3F* vertices, U32 numVertices )
{
   AssertFatal( vertices != NULL, "MathUtils::isPlanarPolygon - Received NULL pointer" );
   AssertFatal( numVertices >= 3, "MathUtils::isPlanarPolygon - Must have at least three vertices" );

   // Triangles are always planar.  Letting smaller numVertices
   // slip through provides robustness for errors in release builds.
   
   if( numVertices <= 3 )
      return true;

   // Compute the normal of the first triangle in the polygon.
   
   Point3F triangle1Normal = mTriangleNormal( vertices[ 0 ], vertices[ 1 ], vertices[ 2 ] );

   // Now go through all the remaining vertices and build triangles
   // with the first two vertices.  Then the normals of all these triangles
   // must be the same (minus some variance due to floating-point inaccuracies)
   // as the normal of the first triangle.

   for( U32 i = 3; i < numVertices; ++ i )
   {
      Point3F triangle2Normal = mTriangleNormal( vertices[ 0 ], vertices[ 1 ], vertices[ i ] );
      if( !triangle1Normal.equal( triangle2Normal ) )
         return false;
   }

   return true;
}
#29
02/04/2011 (8:31 am)
Finally, add the following method to Box3F:

Box3F Box3F::aroundPoints( const Point3F* points, U32 numPoints )
{
   AssertFatal( points != NULL, "Box3F::aroundPoints - Receive a NULL pointer" );
   AssertFatal( numPoints >= 1, "Box3F::aroundPoints - Must have at least one point" );

   Box3F box;
   
   Point3F minPoint = points[ 0 ];
   Point3F maxPoint = points[ 0 ];

   for( U32 i = 1; i < numPoints; ++ i )
      for( U32 n = 0; n < 3; ++ n )
      {
         minPoint[ n ] = getMin( minPoint[ n ], points[ i ][ n ] );
         maxPoint[ n ] = getMax( maxPoint[ n ], points[ i ][ n ] );
      }

   return Box3F( minPoint, maxPoint );
}

Sorry for the lengthy changes but that's it. So far, everything appears to be stable and correct even when using some crazy placement and rotation of portals.

Be aware that your portals need to still make sense, though, in order for the visibility culling to come up with correct results. Remember that portals in Torque are two-way rectangular "peek holes" through which the frustum culling in the engine steps.
#30
02/04/2011 (9:27 am)
Rene,
Any chance of a more idiot-proof explanation of implementing this?

Specifically which file does PlaneF go in?

My errors for all files modded suggest that I'm doing it wrong.
#31
02/04/2011 (10:35 am)
I'm guessing if you're adding functions - don't forget to add the function to the header files as well?
#32
02/04/2011 (11:00 am)
a bit of thinking "away" from this thread...
looking at the code changes.. it could be very possible that those changes can be applied in TGE too, and *hopefully* could fix a similar issue there too... (thou the bug in TGE is a bit different but could be related).
#33
02/04/2011 (1:57 pm)
@Rene:

Adding this to T3D 1.1 B3 produces a lot of compiler errors, so if you could give us the idiot proof explanation we would be very happy.

#34
02/04/2011 (3:27 pm)
header files
That's what happens when I look at code before getting my first cup of tea ...
#35
02/04/2011 (4:40 pm)
Sorry, guys, was a bit pressed for time and so left a lot of things kinda implicit.

So, here's the step by step...

1. Use the first snippet to replace the method in T3D/portal.cpp and also change the declaration in the header to read

bool generatePortalFrustum(   SceneState* state, Frustum* outFrustum );

2. Put the second snippet in math/mPlane.cpp and add this inside the PlaneF class definition in the mPlane.h header:

/// Clip a convex polygon by the plane.
   ///
   /// The resulting polygon will be the input polygon minus the part on the negative side
   /// of the plane.  The input polygon must be convex and @a inVertices must be in CCW or CW order.
   ///
   /// @param inVertices Array holding the vertices of the input polygon.
   /// @param inNumVertices Number of vertices in @a inVertices.
   /// @param outVertices Array to hold the vertices of the clipped polygon.  Must have space for one additional
   ///   vertex in case the polygon is split by the plane such that an additional vertex appears.  Must not
   ///   be the same as @a inVertices.
   /// @return Number of vertices in the clipped polygon, i.e. number of vertices in @a outVertices.
   ///
   /// @note Be aware that if the polygon fully lies on the negative side of the plane,
   ///   the resulting @a outNumVertices will be zero, i.e. no polygon will result from the clip.
   U32 clipPolygon( Point3F* inVertices, U32 inNumVertices, Point3F* outVertices ) const;

3. Add this snippet inside the MathUtils namespace in math/mathUtils.h:

/// Find out whether the given polygon is planar.
   /// @param vertices Array of vertices of the polygon.
   /// @param numVertices Number of vertices in @a vertices.
   /// @return True if the polygon is planar, false otherwise.
   bool isPlanarPolygon( const Point3F* vertices, U32 numVertices );

and add the corresponding snippet from above at the end (but still inside the MathUtils namespace) in math/mathUtils.cpp.4

4. Finally, add the last snippet from above to the end of math/mBox.cpp and then add this declaration inside the BoxF class in math/mBox.h:

/// Create an AABB that fits around the given point cloud, i.e.
   /// find the minimum and maximum extents of the given point set.
   static Box3F aroundPoints( const Point3F* points, U32 numPoints );

Hope that helps.
#36
02/04/2011 (5:29 pm)
I just found an assert that can be triggered because my clipPolygon method above isn't handling vertices close to the clipping plane correctly. Here's the corrected method:

U32 PlaneF::clipPolygon( Point3F* inVertices, U32 inNumVertices, Point3F* outVertices ) const
{
   AssertFatal( inVertices != NULL, "PlaneF::clipPolygon - Received NULL inVertices" );
   AssertFatal( outVertices != NULL, "PlaneF::clipPolygon - Received NULL outVertices" );

   // We silently accept two-point "polygons" since they will be correctly clipped.
   AssertFatal( inNumVertices >= 2, "PlaneF::clipPolygon - Must have at least two vertices" );

   AssertFatal( MathUtils::isPlanarPolygon( inVertices, inNumVertices ), "PlaneF::clipPolygon - Polygon must be planar" );

   U32 numResultVertices = 0;
   for( U32 i = 0; i < inNumVertices; ++ i )
   {
      const Point3F& vertex1 = inVertices[ i ];
      const Point3F& vertex2 = inVertices[ ( i + 1 ) % inNumVertices ];

      // Find sides of each vertex to determine whether
      // the edge connecting them goes through the plane.

      Side side1 = whichSide( vertex1 );
      Side side2 = whichSide( vertex2 );

      // For our purposes, we treat On as Front.

      if( side1 == On )
         side1 = Front;
      if( side2 == On )
         side2 = Front;

      // Skip edges that are fully behind the plane.

      if( side1 == Back && side2 == Back )
         continue;

      // If the first vertex is in front of the plane,
      // we definitely add it.

      if( side1 == Front )
      {
         outVertices[ numResultVertices ] = vertex1;
         numResultVertices ++;
      }

      // For edges fully in front, we've done all we need to do.

      if( side1 == Front && side2 == Front )
         continue;

      // Split edges that intersect the plane.

      const Point3F* from;
      const Point3F* to;

      if( side1 == Front )
      {
         from = &vertex1;
         to = &vertex2;
      }
      else
      {
         from = &vertex2;
         to = &vertex1;
      }

      F32 dt = intersect( *from, *to );
      outVertices[ numResultVertices ] = *from + ( *to - *from ) * dt;
      numResultVertices ++;

      AssertFatal( numResultVertices <= inNumVertices + 1, "PlaneF::clipPolygon - Generated excessive vertices; input polygon convex?" );
   }

   if( numResultVertices < 3 )
      return 0;

   AssertFatal( !numResultVertices || MathUtils::isPlanarPolygon( outVertices, numResultVertices ), "PlaneF::clipPolygon - Computed polygon isn't planar" );
   
   return numResultVertices;
}

Also, I'm seeing some visibility errors with the Portal object visualizations in the World Editor which I'm pretty sure, though, is something else.
#37
02/04/2011 (7:18 pm)
Quote:
I'm seeing some visibility errors with the Portal object visualizations in the World Editor which I'm pretty sure, though, is something else.

Do you want the good news or the bad news at 4:20am in Germany?

Good news - after adding an include for mathUtil to mPlane.cpp we compiled and it's better ...

Bad News - ... from certain angles.

Note: My brain is currently in full blown 2D art mode and so the other hemisphere is off ... so it's entirely possible that I've ballsed something up but it still compiled.

I'll post a vid detailing the issues soon.

[edit]

#38
02/04/2011 (7:28 pm)
Quote:at 4:20am in Germany?

:)

Quote:Bad News - ... from certain angles.

Argh.

I haven't seen any bad culling yet but I just ran into two bad asserts. Bet I'll be plowing through more portal stuff well into next week. Well, enough for today.

Quote:I'll post a vid detailing the issues soon.

That's great. Thanks Steve.
#39
02/04/2011 (7:29 pm)
At least you didn't shout ... or burst into tears. ;)
#40
02/04/2011 (7:32 pm)
@Steve
Hmm, that looks very fishy. Any chance you could send me that scene?

With it running so stable here, I find it hard to believe it still produces errors as blatant as that. But well, I've yet to prove it doesn't :)

//Edit:
That's some cool concurrent posting we're doing here :)