public Terrain RaySelectNeighbour( Ray ray, Real distanceLimit ) { var modifiedRay = new Ray( ray.Origin, ray.Direction ); // Move back half a square - if we're on the edge of the AABB we might // miss the intersection otherwise; it's ok for everywhere else since // we want the far intersection anyway modifiedRay.Origin = modifiedRay.GetPoint( -this.mWorldSize/this.mSize*0.5f ); // transform into terrain space var tPos = Vector3.Zero; var tDir = Vector3.Zero; ConvertPosition( Space.WorldSpace, modifiedRay.Origin, Space.TerrainSpace, ref tPos ); ConvertDirection( Space.WorldSpace, modifiedRay.Direction, Space.TerrainSpace, ref tDir ); // Discard rays with no lateral component if ( Utility.RealEqual( tDir.x, 0.0f, 1e-4 ) && Utility.RealEqual( tDir.y, 0.0f, 1e-4 ) ) { return null; } var terrainRay = new Ray( tPos, tDir ); // Intersect with boundary planes // Only collide with the positive (exit) side of the plane, because we may be // querying from a point outside ourselves if we've cascaded more than once var dist = Real.MaxValue; IntersectResult intersectResult; if ( tDir.x < 0.0f ) { intersectResult = Utility.Intersects( terrainRay, new Plane( Vector3.UnitX, Vector3.Zero ) ); if ( intersectResult.Hit && intersectResult.Distance < dist ) { dist = intersectResult.Distance; } } else if ( tDir.x > 0.0f ) { intersectResult = Utility.Intersects( terrainRay, new Plane( Vector3.NegativeUnitX, Vector3.UnitX ) ); if ( intersectResult.Hit && intersectResult.Distance < dist ) { dist = intersectResult.Distance; } } if ( tDir.y < 0.0f ) { intersectResult = Utility.Intersects( terrainRay, new Plane( Vector3.UnitY, Vector3.Zero ) ); if ( intersectResult.Hit && intersectResult.Distance < dist ) { dist = intersectResult.Distance; } } else if ( tDir.y > 0.0f ) { intersectResult = Utility.Intersects( terrainRay, new Plane( Vector3.NegativeUnitY, Vector3.UnitY ) ); if ( intersectResult.Hit && intersectResult.Distance < dist ) { dist = intersectResult.Distance; } } // discard out of range if ( dist*this.mWorldSize > distanceLimit ) { return null; } var terrainIntersectPos = terrainRay.GetPoint( dist ); var x = terrainIntersectPos.x; var y = terrainIntersectPos.y; var dx = tDir.x; var dy = tDir.y; // Never return diagonal directions, we will navigate those recursively anyway if ( Utility.RealEqual( x, 1.0f, 1e-4f ) && dx > 0 ) { return GetNeighbour( NeighbourIndex.East ); } else if ( Utility.RealEqual( x, 0.0f, 1e-4f ) && dx < 0 ) { return GetNeighbour( NeighbourIndex.West ); } else if ( Utility.RealEqual( y, 1.0f, 1e-4f ) && dy > 0 ) { return GetNeighbour( NeighbourIndex.North ); } else if ( Utility.RealEqual( y, 0.0f, 1e-4f ) && dy < 0 ) { return GetNeighbour( NeighbourIndex.South ); } return null; }
public void WidenRectByVector( Vector3 vec, Rectangle inRect, Real minHeight, Real maxHeight, ref Rectangle outRect ) { outRect = inRect; var p = new Plane(); switch ( Alignment ) { case Alignment.Align_X_Y: p.Redefine( Vector3.UnitZ, new Vector3( 0, 0, vec.z < 0.0f ? minHeight : maxHeight ) ); break; case Alignment.Align_X_Z: p.Redefine( Vector3.UnitY, new Vector3( 0, vec.y < 0.0f ? minHeight : maxHeight, 0 ) ); break; case Alignment.Align_Y_Z: p.Redefine( Vector3.UnitX, new Vector3( vec.x < 0.0f ? minHeight : maxHeight, 0, 0 ) ); break; } var verticalVal = vec.Dot( p.Normal ); if ( Utility.RealEqual( verticalVal, 0.0f ) ) { return; } var corners = new Vector3[4]; var startHeight = verticalVal < 0.0f ? maxHeight : minHeight; GetPoint( inRect.Left, inRect.Top, startHeight, ref corners[ 0 ] ); GetPoint( inRect.Right - 1, inRect.Top, startHeight, ref corners[ 1 ] ); GetPoint( inRect.Left, inRect.Bottom - 1, startHeight, ref corners[ 2 ] ); GetPoint( inRect.Right - 1, inRect.Bottom - 1, startHeight, ref corners[ 3 ] ); for ( int i = 0; i < 4; ++i ) { var ray = new Ray( corners[ i ] + this.mPos, vec ); var rayHit = ray.Intersects( p ); if ( rayHit.Hit ) { var pt = ray.GetPoint( rayHit.Distance ); // convert back to terrain point var terrainHitPos = Vector3.Zero; GetTerrainPosition( pt, ref terrainHitPos ); // build rectangle which has rounded down & rounded up values // remember right & bottom are exclusive var mergeRect = new Rectangle( (long)terrainHitPos.x*( this.mSize - 1 ), (long)terrainHitPos.y*( this.mSize - 1 ), (long)( terrainHitPos.x*(float)( this.mSize - 1 ) + 0.5f ) + 1, (long)( terrainHitPos.y*(float)( this.mSize - 1 ) + 0.5f ) + 1 ); outRect.Merge( mergeRect ); } } }
public KeyValuePair<bool, Vector3> RayIntersects( Ray ray, bool cascadeToNeighbours, Real distanceLimit ) { KeyValuePair<bool, Vector3> Result; // first step: convert the ray to a local vertex space // we assume terrain to be in the x-z plane, with the [0,0] vertex // at origin and a plane distance of 1 between vertices. // This makes calculations easier. var rayOrigin = ray.Origin - Position; var rayDirection = ray.Direction; // change alignment Vector3 tmp; switch ( Alignment ) { case Alignment.Align_X_Y: Utility.Swap<Real>( ref rayOrigin.y, ref rayOrigin.z ); Utility.Swap<Real>( ref rayDirection.y, ref rayDirection.z ); break; case Alignment.Align_Y_Z: // x = z, z = y, y = -x tmp.x = rayOrigin.z; tmp.z = rayOrigin.y; tmp.y = -rayOrigin.x; rayOrigin = tmp; tmp.x = rayDirection.z; tmp.z = rayDirection.y; tmp.y = -rayDirection.x; rayDirection = tmp; break; case Alignment.Align_X_Z: // already in X/Z but values increase in -Z rayOrigin.z = -rayOrigin.z; rayDirection.z = -rayDirection.z; break; } // readjust coordinate origin rayOrigin.x += this.mWorldSize/2; rayOrigin.z += this.mWorldSize/2; // scale down to vertex level rayOrigin.x /= this.mScale; rayOrigin.z /= this.mScale; rayDirection.x /= this.mScale; rayDirection.z /= this.mScale; rayDirection.Normalize(); var localRay = new Ray( rayOrigin, rayDirection ); // test if the ray actually hits the terrain's bounds var maxHeight = MaxHeight; var minHeight = MinHeight; var aabb = new AxisAlignedBox( new Vector3( 0, minHeight, 0 ), new Vector3( this.mSize, maxHeight, this.mSize ) ); var aabbTest = localRay.Intersects( aabb ); if ( !aabbTest.Hit ) { if ( cascadeToNeighbours ) { var neighbour = RaySelectNeighbour( ray, distanceLimit ); if ( neighbour != null ) { return neighbour.RayIntersects( ray, cascadeToNeighbours, distanceLimit ); } } return new KeyValuePair<bool, Vector3>( false, new Vector3() ); } // get intersection point and move inside var cur = localRay.GetPoint( aabbTest.Distance ); // now check every quad the ray touches var quadX = Utility.Min( Utility.Max( (int)( cur.x ), 0 ), (int)this.mSize - 2 ); var quadZ = Utility.Min( Utility.Max( (int)( cur.z ), 0 ), (int)this.mSize - 2 ); var flipX = ( rayDirection.x < 0 ? 0 : 1 ); var flipZ = ( rayDirection.z < 0 ? 0 : 1 ); var xDir = ( rayDirection.x < 0 ? -1 : 1 ); var zDir = ( rayDirection.z < 0 ? -1 : 1 ); Result = new KeyValuePair<bool, Vector3>( true, Vector3.Zero ); var dummyHighValue = (Real)this.mSize*10000; while ( cur.y >= ( minHeight - 1e-3 ) && cur.y <= ( maxHeight + 1e-3 ) ) { if ( quadX < 0 || quadX >= (int)this.mSize - 1 || quadZ < 0 || quadZ >= (int)this.mSize - 1 ) { break; } Result = CheckQuadIntersection( quadX, quadZ, localRay ); if ( Result.Key ) { break; } // determine next quad to test var xDist = Utility.RealEqual( rayDirection.x, 0.0f ) ? dummyHighValue : ( quadX - cur.x + flipX )/rayDirection.x; var zDist = Utility.RealEqual( rayDirection.z, 0.0f ) ? dummyHighValue : ( quadZ - cur.z + flipZ )/rayDirection.z; if ( xDist < zDist ) { quadX += xDir; cur += rayDirection*xDist; } else { quadZ += zDir; cur += rayDirection*zDist; } } var resVec = Vector3.Zero; if ( Result.Key ) { // transform the point of intersection back to world space resVec = Result.Value; resVec.x *= this.mScale; resVec.z *= this.mScale; resVec.x -= this.mWorldSize/2; resVec.z -= this.mWorldSize/2; switch ( Alignment ) { case Alignment.Align_X_Y: Utility.Swap<Real>( ref resVec.y, ref resVec.z ); break; case Alignment.Align_Y_Z: // z = x, y = z, x = -y tmp.x = -rayOrigin.y; tmp.y = rayOrigin.z; tmp.z = rayOrigin.x; rayOrigin = tmp; break; case Alignment.Align_X_Z: resVec.z = -resVec.z; break; } resVec += Position; } else if ( cascadeToNeighbours ) { var neighbour = RaySelectNeighbour( ray, distanceLimit ); if ( neighbour != null ) { Result = neighbour.RayIntersects( ray, cascadeToNeighbours, distanceLimit ); } } return new KeyValuePair<bool, Vector3>( Result.Key, resVec ); }
protected KeyValuePair<bool, Vector3> CheckQuadIntersection( int x, int z, Ray ray ) { // build the two planes belonging to the quad's triangles Vector3 v1 = new Vector3( x, this.mHeightData[ z + this.mSize*x ], z ), v2 = new Vector3( x + 1, this.mHeightData[ z + this.mSize*( x + 1 ) ], z ), v3 = new Vector3( x, this.mHeightData[ ( z + 1 ) + this.mSize*x ], z + 1 ), v4 = new Vector3( x + 1, this.mHeightData[ ( z + 1 ) + this.mSize*( x + 1 ) ], z + 1 ); Plane p1 = new Plane(), p2 = new Plane(); var oddRow = false; if ( z%2 != 0 ) { /* odd 3---4 | \ | 1---2 */ p1.Redefine( v2, v4, v3 ); p2.Redefine( v1, v2, v3 ); oddRow = true; } else { /* even 3---4 | / | 1---2 */ p1.Redefine( v1, v2, v4 ); p2.Redefine( v1, v4, v3 ); } // Test for intersection with the two planes. // Then test that the intersection points are actually // still inside the triangle (with a small error margin) // Also check which triangle it is in var planeInt = ray.Intersects( p1 ); if ( planeInt.Hit ) { var where = ray.GetPoint( planeInt.Distance ); var rel = where - v1; if ( rel.x >= -0.01 && rel.x <= 1.01 && rel.z >= -0.01 && rel.z <= 1.01 // quad bounds && ( ( rel.x >= rel.z && !oddRow ) || ( rel.x >= ( 1 - rel.z ) && oddRow ) ) ) // triangle bounds { return new KeyValuePair<bool, Vector3>( true, where ); } } planeInt = ray.Intersects( p2 ); if ( planeInt.Hit ) { var where = ray.GetPoint( planeInt.Distance ); var rel = where - v1; if ( rel.x >= -0.01 && rel.x <= 1.01 && rel.z >= -0.01 && rel.z <= 1.01 // quad bounds && ( ( rel.x <= rel.z && !oddRow ) || ( rel.x <= ( 1 - rel.z ) && oddRow ) ) ) // triangle bounds { return new KeyValuePair<bool, Vector3>( true, where ); } } return new KeyValuePair<bool, Vector3>( false, Vector3.Zero ); }
// Check if a portal intersects a ray // NOTE: Kinda using my own invented routine here for quad portals... Better do a lot of testing! public bool intersects( Ray ray ) { // Only check if portal is open if ( mOpen ) { if ( mType == PORTAL_TYPE.PORTAL_TYPE_QUAD ) { // since ogre doesn't have built in support for a quad, I'm going to first // find the intersection point (if any) of the ray and the portal plane. Then // using the intersection point, I take the cross product of each side of the portal // (0,1,intersect), (1,2, intersect), (2,3, intersect), and (3,0,intersect). If // all 4 cross products have vectors pointing in the same direction, then the // intersection point is within the portal, otherwise it is outside. IntersectResult result = ray.Intersects( mDerivedPlane ); if ( result.Hit ) { // the ray intersects the plane, now walk around the edges Vector3 isect = ray.GetPoint( result.Distance ); Vector3 cross, vect1, vect2; Vector3 cross2, vect3, vect4; vect1 = mDerivedCorners[ 1 ] - mDerivedCorners[ 0 ]; vect2 = isect - mDerivedCorners[ 0 ]; cross = vect1.Cross( vect2 ); vect3 = mDerivedCorners[ 2 ] - mDerivedCorners[ 1 ]; vect4 = isect - mDerivedCorners[ 1 ]; cross2 = vect3.Cross( vect4 ); if ( cross.Dot( cross2 ) < 0 ) { return false; } vect1 = mDerivedCorners[ 3 ] - mDerivedCorners[ 2 ]; vect2 = isect - mDerivedCorners[ 2 ]; cross = vect1.Cross( vect2 ); if ( cross.Dot( cross2 ) < 0 ) { return false; } vect1 = mDerivedCorners[ 0 ] - mDerivedCorners[ 3 ]; vect2 = isect - mDerivedCorners[ 3 ]; cross = vect1.Cross( vect2 ); if ( cross.Dot( cross2 ) < 0 ) { return false; } // all cross products pointing same way, so intersect // must be on the inside of the portal! return true; } return false; } else if ( mType == PORTAL_TYPE.PORTAL_TYPE_AABB ) { AxisAlignedBox aabb = new AxisAlignedBox( mDerivedCorners[ 0 ], mDerivedCorners[ 1 ] ); IntersectResult result = ray.Intersects( aabb ); return result.Hit; } else // sphere { IntersectResult result = ray.Intersects( mDerivedSphere ); return result.Hit; } } return false; }