diff --git a/TrueCraft.API/BoundingCylinder.cs b/TrueCraft.API/BoundingCylinder.cs new file mode 100644 index 0000000..6a4da6a --- /dev/null +++ b/TrueCraft.API/BoundingCylinder.cs @@ -0,0 +1,62 @@ +using System; +namespace TrueCraft.API +{ + public struct BoundingCylinder : IEquatable + { + public Vector3 Min; + + public Vector3 Max; + + public double Radius; + + public BoundingCylinder(Vector3 min, Vector3 max, double radius) + { + Min = min; + Max = max; + Radius = radius; + } + + public bool Intersects(Vector3 q) + { + return DistancePointLine(q, Min, Max) < Radius; + } + + public bool Intersects(BoundingBox q) + { + var corners = q.GetCorners(); + for (int i = 0; i < corners.Length; i++) + { + if (Intersects(corners[i])) + return true; + } + return false; + } + + // http://answers.unity3d.com/questions/62644/distance-between-a-ray-and-a-point.html + public static double DistancePointLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd) + { + return (ProjectPointLine(point, lineStart, lineEnd) - point).Distance; + } + + public static Vector3 ProjectPointLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd) + { + var rhs = point - lineStart; + var vector2 = lineEnd - lineStart; + var magnitude = vector2.Distance; + var lhs = vector2; + if (magnitude > 1E-06f) + lhs = lhs / magnitude; + var num2 = Vector3.Dot(lhs, rhs); + if (num2 < 0) num2 = 0; + if (num2 > magnitude) num2 = magnitude; + return lineStart + (lhs * num2); + } + + public bool Equals(BoundingCylinder other) + { + return other.Max == this.Max + && other.Min == this.Min + && other.Radius == this.Radius; + } + } +} \ No newline at end of file diff --git a/TrueCraft.API/TrueCraft.API.csproj b/TrueCraft.API/TrueCraft.API.csproj index 926f818..efda6b0 100644 --- a/TrueCraft.API/TrueCraft.API.csproj +++ b/TrueCraft.API/TrueCraft.API.csproj @@ -127,6 +127,7 @@ + diff --git a/TrueCraft.API/Vector3.cs b/TrueCraft.API/Vector3.cs index 94fe7c8..10f80f0 100644 --- a/TrueCraft.API/Vector3.cs +++ b/TrueCraft.API/Vector3.cs @@ -167,6 +167,42 @@ namespace TrueCraft.API Math.Max(value1.Z, value2.Z) ); } + + /// + /// Calculates the dot product between two vectors. + /// + public static double Dot(Vector3 value1, Vector3 value2) + { + return value1.X * value2.X + value1.Y * value2.Y + value1.Z * value2.Z; + } + + /// + /// Computes the cross product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The cross product of two vectors. + public static Vector3 Cross(Vector3 vector1, Vector3 vector2) + { + Cross(ref vector1, ref vector2, out vector1); + return vector1; + } + + /// + /// Computes the cross product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The cross product of two vectors as an output parameter. + public static void Cross(ref Vector3 vector1, ref Vector3 vector2, out Vector3 result) + { + var x = vector1.Y * vector2.Z - vector2.Y * vector1.Z; + var y = -(vector1.X * vector2.Z - vector2.X * vector1.Z); + var z = vector1.X * vector2.Y - vector2.X * vector1.Y; + result.X = x; + result.Y = y; + result.Z = z; + } #endregion diff --git a/TrueCraft.Core.Test/Physics/PhysicsEngineTest.cs b/TrueCraft.Core.Test/Physics/PhysicsEngineTest.cs index 44d6904..bf1be34 100644 --- a/TrueCraft.Core.Test/Physics/PhysicsEngineTest.cs +++ b/TrueCraft.Core.Test/Physics/PhysicsEngineTest.cs @@ -216,7 +216,6 @@ namespace TrueCraft.Core.Test.Physics } [Test] - [Ignore("Fails until I rewrite the physics engine AGAIN")] public void TestCornerCollision() { var repository = GetBlockRepository(); @@ -233,8 +232,8 @@ namespace TrueCraft.Core.Test.Physics // Test physics.Update(TimeSpan.FromSeconds(1)); - Assert.AreEqual(0, entity.Position.X); - Assert.AreEqual(0, entity.Position.Z); + Assert.AreEqual(-1, entity.Position.X); + Assert.AreEqual(-1, entity.Position.Z); Assert.AreEqual(0, entity.Velocity.X); Assert.AreEqual(0, entity.Velocity.Z); } diff --git a/TrueCraft.Core.Test/TrueCraft.API.Test/TestBoundingCylinder.cs b/TrueCraft.Core.Test/TrueCraft.API.Test/TestBoundingCylinder.cs new file mode 100644 index 0000000..6c736a7 --- /dev/null +++ b/TrueCraft.Core.Test/TrueCraft.API.Test/TestBoundingCylinder.cs @@ -0,0 +1,37 @@ +using NUnit.Framework; +using System; +namespace TrueCraft.API.Test +{ + [TestFixture] + public class TestBoundingCylinder + { + [Test] + public void TestIntersectsPoint() + { + // x + // / + // x + var cylinder = new BoundingCylinder(Vector3.Zero, Vector3.One, 1); + Assert.IsTrue(cylinder.Intersects(cylinder.Min)); + Assert.IsTrue(cylinder.Intersects(cylinder.Max)); + Assert.IsTrue(cylinder.Intersects(cylinder.Min + (Vector3.One / 2))); + Assert.IsTrue(cylinder.Intersects(cylinder.Max - (Vector3.One / 2))); + Assert.IsTrue(cylinder.Intersects(new Vector3(0.25, 0, 0))); + Assert.IsFalse(cylinder.Intersects(new Vector3(5, 5, 5))); + } + + [Test] + public void TestIntersectsBox() + { + // x + // / + // x + var cylinder = new BoundingCylinder(Vector3.Zero, Vector3.One * 10, 3); + var doesNotIntersect = new BoundingBox(Vector3.One * 10 + 5, Vector3.One * 10 + 5); + Assert.IsFalse(cylinder.Intersects(doesNotIntersect)); + var intersects = new BoundingBox(Vector3.Zero, Vector3.One); + Assert.IsTrue(cylinder.Intersects(intersects)); + } + } +} + diff --git a/TrueCraft.Core.Test/TrueCraft.API.Test/TrueCraft.API.Test.csproj b/TrueCraft.Core.Test/TrueCraft.API.Test/TrueCraft.API.Test.csproj new file mode 100644 index 0000000..29c46df --- /dev/null +++ b/TrueCraft.Core.Test/TrueCraft.API.Test/TrueCraft.API.Test.csproj @@ -0,0 +1,48 @@ + + + + Debug + AnyCPU + {90712322-5904-4BCE-8606-C662706B8EAE} + Library + TrueCraft.API.Test + TrueCraft.API.Test + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + ..\..\packages\NUnit.3.4.1\lib\net45\nunit.framework.dll + + + + + + + + + + + {FEE55B54-91B0-4325-A2C3-D576C0B7A81F} + TrueCraft.API + + + + \ No newline at end of file diff --git a/TrueCraft.Core.Test/TrueCraft.API.Test/packages.config b/TrueCraft.Core.Test/TrueCraft.API.Test/packages.config new file mode 100644 index 0000000..f2fd55a --- /dev/null +++ b/TrueCraft.Core.Test/TrueCraft.API.Test/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/TrueCraft.Core/Physics/PhysicsEngine.cs b/TrueCraft.Core/Physics/PhysicsEngine.cs index 94adf7e..4f6d1d3 100644 --- a/TrueCraft.Core/Physics/PhysicsEngine.cs +++ b/TrueCraft.Core/Physics/PhysicsEngine.cs @@ -70,15 +70,91 @@ namespace TrueCraft.Core.Physics aabbEntity.TerrainCollision(collision, before.X < 0 ? Vector3.Left : Vector3.Right); if (TestTerrainCollisionZ(aabbEntity, out collision)) aabbEntity.TerrainCollision(collision, before.Z < 0 ? Vector3.Backwards : Vector3.Forwards); + + if (TestTerrainCollisionCylinder(aabbEntity, out collision)) + aabbEntity.TerrainCollision(collision, before); } entity.EndUpdate(entity.Position + entity.Velocity); - TestTerrainCollisionY(aabbEntity, out collision); } } } } + private BoundingBox GetAABBVelocityBox(IAABBEntity entity) + { + var min = new Vector3( + Math.Min(entity.BoundingBox.Min.X, entity.BoundingBox.Min.X + entity.Velocity.X), + Math.Min(entity.BoundingBox.Min.Y, entity.BoundingBox.Min.Y + entity.Velocity.Y), + Math.Min(entity.BoundingBox.Min.Z, entity.BoundingBox.Min.Z + entity.Velocity.Z) + ); + var max = new Vector3( + Math.Max(entity.BoundingBox.Max.X, entity.BoundingBox.Max.X + entity.Velocity.X), + Math.Max(entity.BoundingBox.Max.Y, entity.BoundingBox.Max.Y + entity.Velocity.Y), + Math.Max(entity.BoundingBox.Max.Z, entity.BoundingBox.Max.Z + entity.Velocity.Z) + ); + return new BoundingBox(min, max); + } + + private void AdjustVelocityForCollision(IAABBEntity entity, BoundingBox problem) + { + var velocity = entity.Velocity; + if (entity.Velocity.X < 0) + velocity.X = entity.BoundingBox.Min.X - problem.Max.X; + if (entity.Velocity.X > 0) + velocity.X = entity.BoundingBox.Max.X - problem.Min.X; + if (entity.Velocity.Y < 0) + velocity.Y = entity.BoundingBox.Min.Y - problem.Max.Y; + if (entity.Velocity.Y > 0) + velocity.Y = entity.BoundingBox.Max.Y - problem.Min.Y; + if (entity.Velocity.Z < 0) + velocity.Z = entity.BoundingBox.Min.Z - problem.Max.Z; + if (entity.Velocity.Z > 0) + velocity.Z = entity.BoundingBox.Max.Z - problem.Min.Z; + entity.Velocity = velocity; + } + + public bool TestTerrainCollisionCylinder(IAABBEntity entity, out Vector3 collisionPoint) + { + collisionPoint = Vector3.Zero; + var testBox = GetAABBVelocityBox(entity); + var testCylinder = new BoundingCylinder(testBox.Min, testBox.Max, + entity.BoundingBox.Min.DistanceTo(entity.BoundingBox.Max)); + + bool collision = false; + for (int x = (int)(Math.Floor(testBox.Min.X)); x <= (int)(Math.Ceiling(testBox.Max.X)); x++) + { + for (int z = (int)(Math.Floor(testBox.Min.Z)); z <= (int)(Math.Ceiling(testBox.Max.Z)); z++) + { + for (int y = (int)(Math.Floor(testBox.Min.Y)); y <= (int)(Math.Ceiling(testBox.Max.Y)); y++) + { + var coords = new Coordinates3D(x, y, z); + if (!World.IsValidPosition(coords)) + continue; + + var _box = BlockPhysicsProvider.GetBoundingBox(World, coords); + if (_box == null) + continue; + + var box = _box.Value.OffsetBy(coords); + if (testCylinder.Intersects(box)) + { + if (testBox.Intersects(box)) + { + collision = true; + AdjustVelocityForCollision(entity, box); + testBox = GetAABBVelocityBox(entity); + testCylinder = new BoundingCylinder(testBox.Min, testBox.Max, + entity.BoundingBox.Min.DistanceTo(entity.BoundingBox.Max)); + collisionPoint = coords; + } + } + } + } + } + return collision; + } + public bool TestTerrainCollisionY(IAABBEntity entity, out Vector3 collisionPoint) { // Things we need to do: diff --git a/TrueCraft.Core/TrueCraft.Core.csproj b/TrueCraft.Core/TrueCraft.Core.csproj index 7624b1b..c1a1700 100644 --- a/TrueCraft.Core/TrueCraft.Core.csproj +++ b/TrueCraft.Core/TrueCraft.Core.csproj @@ -371,16 +371,6 @@ TrueCraft.Profiling - - - - - - - - - - diff --git a/TrueCraft.sln b/TrueCraft.sln index f48ebd5..9d3f441 100644 --- a/TrueCraft.sln +++ b/TrueCraft.sln @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.Core.Test", "True EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.Profiling", "TrueCraft.Profiling\TrueCraft.Profiling.csproj", "{BCA0E139-CF47-43B3-9DC9-D4611C0A2AAD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.API.Test", "TrueCraft.Core.Test\TrueCraft.API.Test\TrueCraft.API.Test.csproj", "{90712322-5904-4BCE-8606-C662706B8EAE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,9 +78,16 @@ Global {FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Optimized Debug|Any CPU.Build.0 = Optimized Debug|Any CPU {FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|Any CPU.ActiveCfg = Release|Any CPU {FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|Any CPU.Build.0 = Release|Any CPU + {90712322-5904-4BCE-8606-C662706B8EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90712322-5904-4BCE-8606-C662706B8EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90712322-5904-4BCE-8606-C662706B8EAE}.Optimized Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90712322-5904-4BCE-8606-C662706B8EAE}.Optimized Debug|Any CPU.Build.0 = Debug|Any CPU + {90712322-5904-4BCE-8606-C662706B8EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90712322-5904-4BCE-8606-C662706B8EAE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {BCFDCD93-C23E-49E6-9767-A887B3C2A709} = {6756B61E-5856-4CA7-90B5-6053763FE7BA} + {90712322-5904-4BCE-8606-C662706B8EAE} = {6756B61E-5856-4CA7-90B5-6053763FE7BA} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 @@ -114,6 +123,26 @@ Global $2.inheritsSet = Mono $2.inheritsScope = text/x-csharp $2.scope = text/x-csharp + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $0.DotNetNamingPolicy = $3 + $3.DirectoryNamespaceAssociation = PrefixedHierarchical + $3.ResourceNamePolicy = FileFormatDefault EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE