Пример #1
0
        private void CheckWithTerrain(IAABBEntity entity, ReadOnlyWorld world)
        {
            Vector3 collisionPoint, collisionDirection;

            if (entity.Position.Y > 0 && entity.Position.Y <= 127) // Don't do checks outside the map
            {
                bool fireEvent = entity.Velocity != Vector3.Zero;
                // Do terrain collisions
                if (AdjustVelocityX(entity, world, out collisionPoint, out collisionDirection))
                {
                    if (fireEvent)
                    {
                        entity.TerrainCollision(collisionPoint, collisionDirection);
                    }
                }
                if (AdjustVelocityY(entity, world, out collisionPoint, out collisionDirection))
                {
                    entity.Velocity *= new Vector3(0.1, 1, 0.1); // TODO: More sophisticated friction
                    if (fireEvent)
                    {
                        entity.TerrainCollision(collisionPoint, collisionDirection);
                    }
                }
                if (AdjustVelocityZ(entity, world, out collisionPoint, out collisionDirection))
                {
                    if (fireEvent)
                    {
                        entity.TerrainCollision(collisionPoint, collisionDirection);
                    }
                }
            }
        }
Пример #2
0
        public MultiplayerClient(TrueCraftUser user)
        {
            User         = user;
            Client       = new TcpClient();
            PacketReader = new PacketReader();
            PacketReader.RegisterCorePackets();
            PacketHandlers = new PacketHandler[0x100];
            Handlers.PacketHandlers.RegisterHandlers(this);
            World     = new ReadOnlyWorld();
            Inventory = new InventoryWindow(null);
            var repo = new BlockRepository();

            repo.DiscoverBlockProviders();
            World.World.BlockRepository = repo;
            World.World.ChunkProvider   = new EmptyGenerator();
            Physics    = new PhysicsEngine(World.World, repo);
            SocketPool = new SocketAsyncEventArgsPool(100, 200, 65536);
            connected  = 0;
            cancel     = new CancellationTokenSource();
            Health     = 20;
            var crafting = new CraftingRepository();

            CraftingRepository = crafting;
            crafting.DiscoverRecipes();
        }
Пример #3
0
 public PhysicsEngine(ReadOnlyWorld world, IBlockPhysicsProvider physicsProvider)
 {
     World                      = world;
     Entities                   = new List <IPhysicsEntity>();
     EntityLock                 = new object();
     LastUpdate                 = DateTime.MinValue;
     BlockPhysicsProvider       = physicsProvider;
     MillisecondsBetweenUpdates = 1000 / 20;
 }
Пример #4
0
 public PhysicsEngine(ReadOnlyWorld world, IBlockPhysicsProvider physicsProvider)
 {
     World = world;
     Entities = new List<IPhysicsEntity>();
     EntityLock = new object();
     LastUpdate = DateTime.MinValue;
     BlockPhysicsProvider = physicsProvider;
     MillisecondsBetweenUpdates = 1000 / 20;
 }
Пример #5
0
        // Thanks to http://gamedev.stackexchange.com/questions/47362/cast-ray-to-select-block-in-voxel-game

        public static Tuple <Coordinates3D, BlockFace> Cast(ReadOnlyWorld world,
                                                            Ray ray, IBlockRepository repository, int posmax, int negmax)
        {
            // TODO: There are more efficient ways of doing this, fwiw

            double min  = negmax * 2;
            var    pick = -Coordinates3D.One;
            var    face = BlockFace.PositiveY;

            for (var x = -posmax; x <= posmax; x++)
            {
                for (var y = -negmax; y <= posmax; y++)
                {
                    for (var z = -posmax; z <= posmax; z++)
                    {
                        var coords = (Coordinates3D)(new Vector3(x, y, z) + ray.Position).Round();
                        if (!world.IsValidPosition(coords))
                        {
                            continue;
                        }
                        var Id = world.GetBlockId(coords);
                        if (Id != 0)
                        {
                            var provider = repository.GetBlockProvider(Id);
                            var box      = provider.InteractiveBoundingBox;
                            if (box != null)
                            {
                                BlockFace _face;
                                var       distance = ray.Intersects(box.Value.OffsetBy(coords.AsVector3()), out _face);
                                if (distance != null && distance.Value < min)
                                {
                                    min  = distance.Value;
                                    pick = coords;
                                    face = _face;
                                }
                            }
                        }
                    }
                }
            }

            if (pick == -Coordinates3D.One)
            {
                return(null);
            }
            return(new Tuple <Coordinates3D, BlockFace>(pick, face));
        }
Пример #6
0
 public MultiplayerClient(TrueCraftUser user)
 {
     User = user;
     Client = new TcpClient();
     PacketReader = new PacketReader();
     PacketReader.RegisterCorePackets();
     PacketHandlers = new PacketHandler[0x100];
     Handlers.PacketHandlers.RegisterHandlers(this);
     World = new ReadOnlyWorld();
     var repo = new BlockRepository();
     repo.DiscoverBlockProviders();
     World.World.BlockRepository = repo;
     World.World.ChunkProvider = new EmptyGenerator();
     Physics = new PhysicsEngine(World, repo);
     SocketPool = new SocketAsyncEventArgsPool(100, 200, 65536);
     connected = 0;
     cancel = new CancellationTokenSource();
 }
Пример #7
0
        // Thanks to http://gamedev.stackexchange.com/questions/47362/cast-ray-to-select-block-in-voxel-game

        public static Tuple<Coordinates3D, BlockFace> Cast(ReadOnlyWorld world,
            Ray ray, IBlockRepository repository, int posmax, int negmax)
        {
            // TODO: There are more efficient ways of doing this, fwiw

            double min = negmax * 2;
            var pick = -Coordinates3D.One;
            var face = BlockFace.PositiveY;
            for (int x = -posmax; x <= posmax; x++)
            {
                for (int y = -negmax; y <= posmax; y++)
                {
                    for (int z = -posmax; z <= posmax; z++)
                    {
                        var coords = (Coordinates3D)(new Vector3(x, y, z) + ray.Position).Round();
                        if (!world.IsValidPosition(coords))
                            continue;
                        var id = world.GetBlockID(coords);
                        if (id != 0)
                        {
                            var provider = repository.GetBlockProvider(id);
                            var box = provider.InteractiveBoundingBox;
                            if (box != null)
                            {
                                BlockFace _face;
                                var distance = ray.Intersects(box.Value.OffsetBy(coords), out _face);
                                if (distance != null && distance.Value < min)
                                {
                                    min = distance.Value;
                                    pick = coords;
                                    face = _face;
                                }
                            }
                        }
                    }
                }
            }
            if (pick == -Coordinates3D.One)
                return null;
            return new Tuple<Coordinates3D, BlockFace>(pick, face);
        }
Пример #8
0
        /// <summary>
        /// Performs terrain collision tests and adjusts the Z-axis velocity accordingly
        /// </summary>
        /// <returns>True if the entity collides with the terrain</returns>
        private bool AdjustVelocityZ(IAABBEntity entity, ReadOnlyWorld world, out Vector3 collision, out Vector3 collisionDirection)
        {
            collision          = Vector3.Zero;
            collisionDirection = Vector3.Zero;
            if (entity.Velocity.Z == 0)
            {
                return(false);
            }
            // Do some enviornment guessing to improve speed
            int minX = (int)entity.Position.X - (entity.Position.X < 0 ? 1 : 0);
            int maxX = (int)(entity.Position.X + entity.Size.Depth) - (entity.Position.X < 0 ? 1 : 0);
            int minY = (int)entity.Position.Y - (entity.Position.Y < 0 ? 1 : 0);
            int maxY = (int)(entity.Position.Y + entity.Size.Width) - (entity.Position.Y < 0 ? 1 : 0);
            int minZ, maxZ;

            // Expand bounding box to include area to be tested
            if (entity.Velocity.Z < 0)
            {
                TempBoundingBox = new BoundingBox(
                    new Vector3(entity.BoundingBox.Min.X, entity.BoundingBox.Min.Y, entity.BoundingBox.Min.Z + entity.Velocity.Z),
                    entity.BoundingBox.Max);

                maxZ = (int)(TempBoundingBox.Max.Z);
                minZ = (int)(TempBoundingBox.Min.Z + entity.Velocity.Z) - 1;
            }
            else
            {
                TempBoundingBox = new BoundingBox(
                    entity.BoundingBox.Min,
                    new Vector3(entity.BoundingBox.Max.X, entity.BoundingBox.Max.Y, entity.BoundingBox.Max.Z + entity.Velocity.Z)
                    );
                minZ = (int)(entity.BoundingBox.Min.Z);
                maxZ = (int)(entity.BoundingBox.Max.Z + entity.Velocity.Z) + 1;
            }

            // Do terrain checks
            double?     collisionPoint = null;
            BoundingBox blockBox;

            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    for (int z = minZ; z <= maxZ; z++)
                    {
                        var position    = new Coordinates3D(x, y, z);
                        var boundingBox = BlockPhysicsProvider.GetBoundingBox(world.World, position);
                        if (boundingBox == null)
                        {
                            continue;
                        }
                        blockBox = boundingBox.Value.OffsetBy(position + new Vector3(0.5));
                        if (TempBoundingBox.Intersects(blockBox))
                        {
                            if (entity.Velocity.Z < 0)
                            {
                                if (!collisionPoint.HasValue)
                                {
                                    collisionPoint = blockBox.Max.Z;
                                }
                                else if (collisionPoint.Value < blockBox.Max.Z)
                                {
                                    collisionPoint = blockBox.Max.Z;
                                }
                            }
                            else
                            {
                                if (!collisionPoint.HasValue)
                                {
                                    collisionPoint = blockBox.Min.Z;
                                }
                                else if (collisionPoint.Value > blockBox.Min.Z)
                                {
                                    collisionPoint = blockBox.Min.Z;
                                }
                            }
                            collision = position;
                        }
                    }
                }
            }

            if (collisionPoint != null)
            {
                if (entity.Velocity.Z < 0)
                {
                    entity.Velocity = new Vector3(
                        entity.Velocity.X,
                        entity.Velocity.Y,
                        entity.Velocity.Z - (TempBoundingBox.Min.Z - collisionPoint.Value));
                    collisionDirection = Vector3.Backwards;
                }
                else if (entity.Velocity.Z > 0)
                {
                    entity.Velocity = new Vector3(
                        entity.Velocity.X,
                        entity.Velocity.Y,
                        entity.Velocity.Z - (TempBoundingBox.Max.Z - collisionPoint.Value));
                    collisionDirection = Vector3.Forwards;
                }
                return(true);
            }

            return(false);
        }
Пример #9
0
        /// <summary>
        /// Performs terrain collision tests and adjusts the Y-axis velocity accordingly
        /// </summary>
        /// <returns>True if the entity collides with the terrain</returns>
        private bool AdjustVelocityY(IAABBEntity entity, ReadOnlyWorld world, out Vector3 collision, out Vector3 collisionDirection)
        {
            collision          = Vector3.Zero;
            collisionDirection = Vector3.Zero;
            if (entity.Velocity.Y == 0)
            {
                return(false);
            }
            // Do some enviornment guessing to improve speed
            int minX = (int)entity.Position.X - (entity.Position.X < 0 ? 1 : 0);
            int maxX = (int)(entity.Position.X + entity.Size.Width) - (entity.Position.X < 0 ? 1 : 0);
            int minZ = (int)entity.Position.Z - (entity.Position.Z < 0 ? 1 : 0);
            int maxZ = (int)(entity.Position.Z + entity.Size.Depth) - (entity.Position.Z < 0 ? 1 : 0);
            int minY, maxY;

            // Expand bounding box to include area to be tested
            if (entity.Velocity.Y < 0)
            {
                TempBoundingBox = new BoundingBox(
                    new Vector3(entity.BoundingBox.Min.X, entity.BoundingBox.Min.Y + entity.Velocity.Y, entity.BoundingBox.Min.Z),
                    entity.BoundingBox.Max);

                maxY = (int)(TempBoundingBox.Max.Y);
                minY = (int)(TempBoundingBox.Min.Y + entity.Velocity.Y) - 1;
            }
            else
            {
                TempBoundingBox = new BoundingBox(
                    entity.BoundingBox.Min,
                    new Vector3(entity.BoundingBox.Max.X, entity.BoundingBox.Max.Y + entity.Velocity.Y, entity.BoundingBox.Max.Z));
                minY = (int)(entity.BoundingBox.Min.Y);
                maxY = (int)(entity.BoundingBox.Max.Y + entity.Velocity.Y) + 1;
            }

            // Clamp Y into map boundaries
            if (minY < 0)
            {
                minY = 0;
            }
            if (minY >= TrueCraft.Core.World.World.Height)
            {
                minY = TrueCraft.Core.World.World.Height - 1;
            }
            if (maxY < 0)
            {
                maxY = 0;
            }
            if (maxY >= TrueCraft.Core.World.World.Height)
            {
                maxY = TrueCraft.Core.World.World.Height - 1;
            }

            // Do terrain checks
            double?     collisionPoint = null;
            BoundingBox blockBox;

            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    for (int z = minZ; z <= maxZ; z++)
                    {
                        var position = new Coordinates3D(x, y, z);
                        if (!World.IsValidPosition(position))
                        {
                            continue;
                        }
                        var boundingBox = BlockPhysicsProvider.GetBoundingBox(world.World, position);
                        if (boundingBox == null)
                        {
                            continue;
                        }
                        blockBox = boundingBox.Value.OffsetBy(position + new Vector3(0.5));
                        if (TempBoundingBox.Intersects(blockBox))
                        {
                            if (entity.Velocity.Y < 0)
                            {
                                if (!collisionPoint.HasValue)
                                {
                                    collisionPoint = blockBox.Max.Y;
                                }
                                else if (collisionPoint.Value < blockBox.Max.Y)
                                {
                                    collisionPoint = blockBox.Max.Y;
                                }
                            }
                            else
                            {
                                if (!collisionPoint.HasValue)
                                {
                                    collisionPoint = blockBox.Min.Y;
                                }
                                else if (collisionPoint.Value > blockBox.Min.Y)
                                {
                                    collisionPoint = blockBox.Min.Y;
                                }
                            }
                            collision = position;
                        }
                    }
                }
            }

            if (collisionPoint != null)
            {
                if (entity.Velocity.Y < 0)
                {
                    // TODO: Do block event
                    //var block = world.GetBlock(collision);
                    //block.OnBlockWalkedOn(world, collision, this);
                    entity.Velocity = new Vector3(entity.Velocity.X,
                                                  entity.Velocity.Y + (collisionPoint.Value - TempBoundingBox.Min.Y),
                                                  entity.Velocity.Z);
                    collisionDirection = Vector3.Down;
                }
                else if (entity.Velocity.Y > 0)
                {
                    entity.Velocity = new Vector3(entity.Velocity.X,
                                                  entity.Velocity.Y - (TempBoundingBox.Max.Y - collisionPoint.Value),
                                                  entity.Velocity.Z);
                    collisionDirection = Vector3.Up;
                }
                return(true);
            }

            return(false);
        }
Пример #10
0
 private void CheckWithTerrain(IAABBEntity entity, ReadOnlyWorld world)
 {
     Vector3 collisionPoint, collisionDirection;
     if (entity.Position.Y > 0 && entity.Position.Y <= 127) // Don't do checks outside the map
     {
         bool fireEvent = entity.Velocity != Vector3.Zero;
         // Do terrain collisions
         if (AdjustVelocityX(entity, world, out collisionPoint, out collisionDirection))
         {
             if (fireEvent)
                 entity.TerrainCollision(collisionPoint, collisionDirection);
         }
         if (AdjustVelocityY(entity, world, out collisionPoint, out collisionDirection))
         {
             entity.Velocity *= new Vector3(0.1, 1, 0.1); // TODO: More sophisticated friction
             if (fireEvent)
                 entity.TerrainCollision(collisionPoint, collisionDirection);
         }
         if (AdjustVelocityZ(entity, world, out collisionPoint, out collisionDirection))
         {
             if (fireEvent)
                 entity.TerrainCollision(collisionPoint, collisionDirection);
         }
     }
 }
Пример #11
0
        /// <summary>
        /// Performs terrain collision tests and adjusts the Z-axis velocity accordingly
        /// </summary>
        /// <returns>True if the entity collides with the terrain</returns>
        private bool AdjustVelocityZ(IAABBEntity entity, ReadOnlyWorld world, out Vector3 collision, out Vector3 collisionDirection)
        {
            collision = Vector3.Zero;
            collisionDirection = Vector3.Zero;
            if (entity.Velocity.Z == 0)
                return false;
            // Do some enviornment guessing to improve speed
            int minX = (int)entity.Position.X - (entity.Position.X < 0 ? 1 : 0);
            int maxX = (int)(entity.Position.X + entity.Size.Depth) - (entity.Position.X < 0 ? 1 : 0);
            int minY = (int)entity.Position.Y - (entity.Position.Y < 0 ? 1 : 0);
            int maxY = (int)(entity.Position.Y + entity.Size.Width) - (entity.Position.Y < 0 ? 1 : 0);
            int minZ, maxZ;

            // Expand bounding box to include area to be tested
            if (entity.Velocity.Z < 0)
            {
                TempBoundingBox = new BoundingBox(
                    new Vector3(entity.BoundingBox.Min.X, entity.BoundingBox.Min.Y, entity.BoundingBox.Min.Z + entity.Velocity.Z),
                    entity.BoundingBox.Max);

                maxZ = (int)(TempBoundingBox.Max.Z);
                minZ = (int)(TempBoundingBox.Min.Z + entity.Velocity.Z) - 1;
            }
            else
            {
                TempBoundingBox = new BoundingBox(
                    entity.BoundingBox.Min,
                    new Vector3(entity.BoundingBox.Max.X, entity.BoundingBox.Max.Y, entity.BoundingBox.Max.Z + entity.Velocity.Z)
                );
                minZ = (int)(entity.BoundingBox.Min.Z);
                maxZ = (int)(entity.BoundingBox.Max.Z + entity.Velocity.Z) + 1;
            }

            // Do terrain checks
            double? collisionPoint = null;
            BoundingBox blockBox;
            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    for (int z = minZ; z <= maxZ; z++)
                    {
                        var position = new Coordinates3D(x, y, z);
                        var boundingBox = BlockPhysicsProvider.GetBoundingBox(world.World, position);
                        if (boundingBox == null)
                            continue;
                        blockBox = boundingBox.Value.OffsetBy(position + new Vector3(0.5));
                        if (TempBoundingBox.Intersects(blockBox))
                        {
                            if (entity.Velocity.Z < 0)
                            {
                                if (!collisionPoint.HasValue)
                                    collisionPoint = blockBox.Max.Z;
                                else if (collisionPoint.Value < blockBox.Max.Z)
                                    collisionPoint = blockBox.Max.Z;
                            }
                            else
                            {
                                if (!collisionPoint.HasValue)
                                    collisionPoint = blockBox.Min.Z;
                                else if (collisionPoint.Value > blockBox.Min.Z)
                                    collisionPoint = blockBox.Min.Z;
                            }
                            collision = position;
                        }
                    }
                }
            }

            if (collisionPoint != null)
            {
                if (entity.Velocity.Z < 0)
                {
                    entity.Velocity = new Vector3(
                        entity.Velocity.X,
                        entity.Velocity.Y,
                        entity.Velocity.Z - (TempBoundingBox.Min.Z - collisionPoint.Value));
                    collisionDirection = Vector3.Backwards;
                }
                else if (entity.Velocity.Z > 0)
                {
                    entity.Velocity = new Vector3(
                        entity.Velocity.X,
                        entity.Velocity.Y,
                        entity.Velocity.Z - (TempBoundingBox.Max.Z - collisionPoint.Value));
                    collisionDirection = Vector3.Forwards;
                }
                return true;
            }

            return false;
        }
Пример #12
0
        /// <summary>
        /// Performs terrain collision tests and adjusts the Y-axis velocity accordingly
        /// </summary>
        /// <returns>True if the entity collides with the terrain</returns>
        private bool AdjustVelocityY(IAABBEntity entity, ReadOnlyWorld world, out Vector3 collision, out Vector3 collisionDirection)
        {
            collision = Vector3.Zero;
            collisionDirection = Vector3.Zero;
            if (entity.Velocity.Y == 0)
                return false;
            // Do some enviornment guessing to improve speed
            int minX = (int)entity.Position.X - (entity.Position.X < 0 ? 1 : 0);
            int maxX = (int)(entity.Position.X + entity.Size.Width) - (entity.Position.X < 0 ? 1 : 0);
            int minZ = (int)entity.Position.Z - (entity.Position.Z < 0 ? 1 : 0);
            int maxZ = (int)(entity.Position.Z + entity.Size.Depth) - (entity.Position.Z < 0 ? 1 : 0);
            int minY, maxY;

            // Expand bounding box to include area to be tested
            if (entity.Velocity.Y < 0)
            {
                TempBoundingBox = new BoundingBox(
                    new Vector3(entity.BoundingBox.Min.X, entity.BoundingBox.Min.Y + entity.Velocity.Y, entity.BoundingBox.Min.Z),
                    entity.BoundingBox.Max);

                maxY = (int)(TempBoundingBox.Max.Y);
                minY = (int)(TempBoundingBox.Min.Y + entity.Velocity.Y) - 1;
            }
            else
            {
                TempBoundingBox = new BoundingBox(
                    entity.BoundingBox.Min,
                    new Vector3(entity.BoundingBox.Max.X, entity.BoundingBox.Max.Y + entity.Velocity.Y, entity.BoundingBox.Max.Z));
                minY = (int)(entity.BoundingBox.Min.Y);
                maxY = (int)(entity.BoundingBox.Max.Y + entity.Velocity.Y) + 1;
            }

            // Clamp Y into map boundaries
            if (minY < 0)
                minY = 0;
            if (minY >= TrueCraft.Core.World.World.Height)
                minY = TrueCraft.Core.World.World.Height - 1;
            if (maxY < 0)
                maxY = 0;
            if (maxY >= TrueCraft.Core.World.World.Height)
                maxY = TrueCraft.Core.World.World.Height - 1;

            // Do terrain checks
            double? collisionPoint = null;
            BoundingBox blockBox;
            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    for (int z = minZ; z <= maxZ; z++)
                    {
                        var position = new Coordinates3D(x, y, z);
                        if (!World.IsValidPosition(position))
                            continue;
                        var boundingBox = BlockPhysicsProvider.GetBoundingBox(world.World, position);
                        if (boundingBox == null)
                            continue;
                        blockBox = boundingBox.Value.OffsetBy(position + new Vector3(0.5));
                        if (TempBoundingBox.Intersects(blockBox))
                        {
                            if (entity.Velocity.Y < 0)
                            {
                                if (!collisionPoint.HasValue)
                                    collisionPoint = blockBox.Max.Y;
                                else if (collisionPoint.Value < blockBox.Max.Y)
                                    collisionPoint = blockBox.Max.Y;
                            }
                            else
                            {
                                if (!collisionPoint.HasValue)
                                    collisionPoint = blockBox.Min.Y;
                                else if (collisionPoint.Value > blockBox.Min.Y)
                                    collisionPoint = blockBox.Min.Y;
                            }
                            collision = position;
                        }
                    }
                }
            }

            if (collisionPoint != null)
            {
                if (entity.Velocity.Y < 0)
                {
                    // TODO: Do block event
                    //var block = world.GetBlock(collision);
                    //block.OnBlockWalkedOn(world, collision, this);
                    entity.Velocity = new Vector3(entity.Velocity.X,
                        entity.Velocity.Y + (collisionPoint.Value - TempBoundingBox.Min.Y),
                        entity.Velocity.Z);
                    collisionDirection = Vector3.Down;
                }
                else if (entity.Velocity.Y > 0)
                {
                    entity.Velocity = new Vector3(entity.Velocity.X,
                        entity.Velocity.Y - (TempBoundingBox.Max.Y - collisionPoint.Value),
                        entity.Velocity.Z);
                    collisionDirection = Vector3.Up;
                }
                return true;
            }

            return false;
        }
Пример #13
0
        // Thanks to http://gamedev.stackexchange.com/questions/47362/cast-ray-to-select-block-in-voxel-game
        public static Tuple<Coordinates3D, BlockFace> Cast(ReadOnlyWorld world,
            Ray ray, IBlockRepository repository, double max)
        {
            var origin = ray.Position.Floor();
            var direction = ray.Direction;
            var step = new Vector3(SigNum(ray.Direction.X), SigNum(ray.Direction.Y), SigNum(ray.Direction.Z));
            var tMax = new Vector3(
                IntBound(origin.X, direction.X),
                IntBound(origin.Y, direction.Y),
                IntBound(origin.Z, direction.Z));
            var tDelta = new Vector3(
                step.X / direction.X,
                step.Y / direction.Y,
                step.Z / direction.Z);
            BlockFace face = BlockFace.PositiveY;

            if (ray.Direction == Vector3.Zero)
                return null;

            max /= Math.Sqrt(ray.Direction.X * ray.Direction.X
                + ray.Direction.Y * ray.Direction.Y
                + ray.Direction.Z * ray.Direction.Z);

            while (world.IsValidPosition((Coordinates3D)origin))
            {
                var provider = repository.GetBlockProvider(world.GetBlockID((Coordinates3D)origin));
                var _box = provider.BoundingBox;
                if (_box != null)
                {
                    var box = _box.Value.OffsetBy((Coordinates3D)origin);
                    if (ray.Intersects(box) != null)
                        return new Tuple<Coordinates3D, BlockFace>((Coordinates3D)origin, face);
                }

                if (tMax.X < tMax.Y)
                {
                    if (tMax.X < tMax.Z)
                    {
                        if (tMax.X > max)
                            return null;
                        // Update which cube we are now in.
                        origin.X += step.X;
                        // Adjust tMaxX to the next X-oriented boundary crossing.
                        tMax.X += tDelta.X;
                        // Record the normal vector of the cube face we entered.
                        if (step.X < 0)
                            face = BlockFace.PositiveX;
                        else
                            face = BlockFace.NegativeX;
                    }
                    else
                    {
                        if (tMax.Z > max)
                            return null;
                        origin.Z += step.Z;
                        tMax.Z += tDelta.Z;
                        if (step.Z < 0)
                            face = BlockFace.PositiveZ;
                        else
                            face = BlockFace.NegativeZ;
                    }
                }
                else
                {
                    if (tMax.Y < tMax.Z)
                    {
                        if (tMax.Y > max)
                            return null;
                        origin.Y += step.Y;
                        tMax.Y += tDelta.Y;
                        if (step.Y < 0)
                            face = BlockFace.PositiveY;
                        else
                            face = BlockFace.NegativeY;
                    }
                    else
                    {
                        // Identical to the second case, repeated for simplicity in
                        // the conditionals.
                        if (tMax.Z > max)
                            break;
                        origin.Z += step.Z;
                        tMax.Z += tDelta.Z;
                        if (step.Z < 0)
                            face = BlockFace.PositiveZ;
                        else
                            face = BlockFace.NegativeZ;
                    }
                }
            }

            return null;
        }