diff --git a/ClassicalSharp/Game/Game.cs b/ClassicalSharp/Game/Game.cs index f75243461..f0b3132a4 100644 --- a/ClassicalSharp/Game/Game.cs +++ b/ClassicalSharp/Game/Game.cs @@ -441,7 +441,7 @@ namespace ClassicalSharp { } internal bool CanPick( byte block ) { - if( BlockInfo.CollideType[block] == BlockCollideType.WalkThrough ) + if( BlockInfo.IsAir[block] ) return false; if( !BlockInfo.IsLiquid[block] ) return true; diff --git a/ClassicalSharp/Physics/Picking.cs b/ClassicalSharp/Physics/Picking.cs index dcff861e8..65a75e0a3 100644 --- a/ClassicalSharp/Physics/Picking.cs +++ b/ClassicalSharp/Physics/Picking.cs @@ -6,9 +6,9 @@ namespace ClassicalSharp { public static class Picking { // http://www.xnawiki.com/index.php/Voxel_traversal - //https://web.archive.org/web/20120113051728/http://www.xnawiki.com/index.php?title=Voxel_traversal + // https://web.archive.org/web/20120113051728/http://www.xnawiki.com/index.php?title=Voxel_traversal /// Determines the picked block based on the given origin and direction vector.
- /// Marks pickedPos as invalid if a block could not be found due to going outside map boundaries + /// Marks pickedPos as invalid if a block could not be found due to going outside map boundaries /// or not being able to find a suitable candiate within the given reach distance.
public static void CalculatePickedBlock( Game window, Vector3 origin, Vector3 dir, float reach, PickedPos pickedPos ) { // Implementation based on: "A Fast Voxel Traversal Algorithm for Ray Tracing" @@ -19,32 +19,9 @@ namespace ClassicalSharp { // The cell in which the ray starts. Vector3I start = Vector3I.Floor( origin ); // Rounds the position's X, Y and Z down to the nearest integer values. int x = start.X, y = start.Y, z = start.Z; - // Determine which way we go. - int stepX = Math.Sign( dir.X ), stepY = Math.Sign( dir.Y ), stepZ = Math.Sign( dir.Z ); - - // Calculate cell boundaries. When the step (i.e. direction sign) is positive, - // the next boundary is AFTER our current position, meaning that we have to add 1. - // Otherwise, it is BEFORE our current position, in which case we add nothing. - Vector3I cellBoundary = new Vector3I( - x + (stepX > 0 ? 1 : 0), - y + (stepY > 0 ? 1 : 0), - z + (stepZ > 0 ? 1 : 0) ); - - // NOTE: we want it so if dir.x = 0, tmax.x = positive infinity - // Determine how far we can travel along the ray before we hit a voxel boundary. - Vector3 tMax = new Vector3( - (cellBoundary.X - origin.X) / dir.X, // Boundary is a plane on the YZ axis. - (cellBoundary.Y - origin.Y) / dir.Y, // Boundary is a plane on the XZ axis. - (cellBoundary.Z - origin.Z) / dir.Z ); // Boundary is a plane on the XY axis. - if( Single.IsNaN( tMax.X ) || Single.IsInfinity( tMax.X ) ) tMax.X = Single.PositiveInfinity; - if( Single.IsNaN( tMax.Y ) || Single.IsInfinity( tMax.Y ) ) tMax.Y = Single.PositiveInfinity; - if( Single.IsNaN( tMax.Z ) || Single.IsInfinity( tMax.Z ) ) tMax.Z = Single.PositiveInfinity; - - // Determine how far we must travel along the ray before we have crossed a gridcell. - Vector3 tDelta = new Vector3( stepX / dir.X, stepY / dir.Y, stepZ / dir.Z ); - if( Single.IsNaN( tDelta.X ) ) tDelta.X = Single.PositiveInfinity; - if( Single.IsNaN( tDelta.Y ) ) tDelta.Y = Single.PositiveInfinity; - if( Single.IsNaN( tDelta.Z ) ) tDelta.Z = Single.PositiveInfinity; + Vector3I step, cellBoundary; + Vector3 tMax, tDelta; + CalculateTimes( origin, dir, out step, out cellBoundary, out tMax, out tDelta ); Map map = window.Map; BlockInfo info = window.BlockInfo; @@ -69,7 +46,7 @@ namespace ClassicalSharp { if( window.CanPick( block ) ) { // This cell falls on the path of the ray. Now perform an additional bounding box test, - // since some blocks do not occupy a whole cell. + // since some blocks do not occupy a whole cell. float t0, t1; if( Intersection.RayIntersectsBox( origin, dir, min, max, out t0, out t1 ) ) { Vector3 intersect = origin + dir * t0; @@ -77,26 +54,100 @@ namespace ClassicalSharp { return; } } + Step( ref tMax, ref tDelta, ref step, ref x, ref y, ref z ); + iterations++; + } + throw new InvalidOperationException( "did over 10000 iterations in CalculatePickedBlock(). " + + "Something has gone wrong. (dir: " + dir + ")" ); + } + + public static void ClipCameraPos( Game window, Vector3 origin, Vector3 dir, float reach, PickedPos pickedPos ) { + Vector3I start = Vector3I.Floor( origin ); + int x = start.X, y = start.Y, z = start.Z; + Vector3I step, cellBoundary; + Vector3 tMax, tDelta; + CalculateTimes( origin, dir, out step, out cellBoundary, out tMax, out tDelta ); + + Map map = window.Map; + BlockInfo info = window.BlockInfo; + float reachSquared = reach * reach; + int iterations = 0; + + while( iterations < 10000 ) { + byte block = GetBlock( map, x, y, z, origin ); + Vector3 min = new Vector3( x, y, z ) + info.MinBB[block]; + Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block]; - if( tMax.X < tMax.Y && tMax.X < tMax.Z ) { - // tMax.X is the lowest, an YZ cell boundary plane is nearest. - x += stepX; - tMax.X += tDelta.X; - } else if( tMax.Y < tMax.Z ) { - // tMax.Y is the lowest, an XZ cell boundary plane is nearest. - y += stepY; - tMax.Y += tDelta.Y; - } else { - // tMax.Z is the lowest, an XY cell boundary plane is nearest. - z += stepZ; - tMax.Z += tDelta.Z; + float dx = Math.Min( Math.Abs( origin.X - min.X ), Math.Abs( origin.X - max.X ) ); + float dy = Math.Min( Math.Abs( origin.Y - min.Y ), Math.Abs( origin.Y - max.Y ) ); + float dz = Math.Min( Math.Abs( origin.Z - min.Z ), Math.Abs( origin.Z - max.Z ) ); + + if( dx * dx + dy * dy + dz * dz > reachSquared ) { + pickedPos.SetAsInvalid(); + return; } + + if( info.CollideType[block] == BlockCollideType.Solid && !info.IsAir[block] ) { + float t0, t1; + if( Intersection.RayIntersectsBox( origin, dir, min, max, out t0, out t1 ) ) { + Vector3 intersect = origin + dir * t0; + pickedPos.SetAsValid( x, y, z, min, max, block, intersect ); + return; + } + } + Step( ref tMax, ref tDelta, ref step, ref x, ref y, ref z ); iterations++; } throw new InvalidOperationException( "did over 10000 iterations in GetPickedBlockPos(). " + "Something has gone wrong. (dir: " + dir + ")" ); } + static void CalculateTimes( Vector3 origin, Vector3 dir, out Vector3I step, + out Vector3I cellBoundary, out Vector3 tMax, out Vector3 tDelta ) { + Vector3I start = Vector3I.Floor( origin ); + // Determine which way we go. + step.X = Math.Sign( dir.X ); step.Y = Math.Sign( dir.Y ); step.Z = Math.Sign( dir.Z ); + // Calculate cell boundaries. When the step (i.e. direction sign) is positive, + // the next boundary is AFTER our current position, meaning that we have to add 1. + // Otherwise, it is BEFORE our current position, in which case we add nothing. + cellBoundary = new Vector3I( + start.X + (step.X > 0 ? 1 : 0), + start.Y + (step.Y > 0 ? 1 : 0), + start.Z + (step.Z > 0 ? 1 : 0) ); + + // NOTE: we want it so if dir.x = 0, tmax.x = positive infinity + // Determine how far we can travel along the ray before we hit a voxel boundary. + tMax = new Vector3( + (cellBoundary.X - origin.X) / dir.X, // Boundary is a plane on the YZ axis. + (cellBoundary.Y - origin.Y) / dir.Y, // Boundary is a plane on the XZ axis. + (cellBoundary.Z - origin.Z) / dir.Z ); // Boundary is a plane on the XY axis. + if( Single.IsNaN( tMax.X ) || Single.IsInfinity( tMax.X ) ) tMax.X = Single.PositiveInfinity; + if( Single.IsNaN( tMax.Y ) || Single.IsInfinity( tMax.Y ) ) tMax.Y = Single.PositiveInfinity; + if( Single.IsNaN( tMax.Z ) || Single.IsInfinity( tMax.Z ) ) tMax.Z = Single.PositiveInfinity; + + // Determine how far we must travel along the ray before we have crossed a gridcell. + tDelta = new Vector3( step.X / dir.X, step.Y / dir.Y, step.Z / dir.Z ); + if( Single.IsNaN( tDelta.X ) ) tDelta.X = Single.PositiveInfinity; + if( Single.IsNaN( tDelta.Y ) ) tDelta.Y = Single.PositiveInfinity; + if( Single.IsNaN( tDelta.Z ) ) tDelta.Z = Single.PositiveInfinity; + } + + static void Step( ref Vector3 tMax, ref Vector3 tDelta, ref Vector3I step, ref int x, ref int y, ref int z ) { + if( tMax.X < tMax.Y && tMax.X < tMax.Z ) { + // tMax.X is the lowest, an YZ cell boundary plane is nearest. + x += step.X; + tMax.X += tDelta.X; + } else if( tMax.Y < tMax.Z ) { + // tMax.Y is the lowest, an XZ cell boundary plane is nearest. + y += step.Y; + tMax.Y += tDelta.Y; + } else { + // tMax.Z is the lowest, an XY cell boundary plane is nearest. + z += step.Z; + tMax.Z += tDelta.Z; + } + } + static byte GetBlock( Map map, int x, int y, int z, Vector3 origin ) { if( x >= 0 && z >= 0 && x < map.Width && z < map.Length ) { if( y >= map.Height ) return 0; diff --git a/ClassicalSharp/Utils/Camera.cs b/ClassicalSharp/Utils/Camera.cs index 96f3cd244..871d24d08 100644 --- a/ClassicalSharp/Utils/Camera.cs +++ b/ClassicalSharp/Utils/Camera.cs @@ -144,6 +144,7 @@ namespace ClassicalSharp { Vector3 eyePos = player.EyePosition; eyePos.Y += bobYOffset; Vector3 cameraPos = eyePos - Utils.GetDirVector( player.YawRadians, player.PitchRadians ) * distance; + //Vector3 cameraPos = Picking.ClipCameraPos( game, eyePos, -Utils.GetDirVector( player.YawRadians, player.PitchRadians ), distance ); return Matrix4.LookAt( cameraPos, eyePos, Vector3.UnitY ) * tiltMatrix; }