public void TestDisconnectReturnsNull() { GeometryCollection geometry = new GeometryCollection(); Line line = new Line(new Vector2(-1, 0), new Vector2(1, 0)); geometry.Add(line); geometry.Add(new Vector2(2, 0)); BoundParticle particle = new BoundParticle(line, false, 0.5, 0, 0); Path path = PathFinding.PathTo(particle, new Vector2(2, 0), geometry, 0.1f, 0.1f); Assert.IsNull(path); }
public void TestBowlSwing() { GeometryCollection geometry = new GeometryCollection(); Line line = new Line(new Vector2(1, 0), new Vector2(-1, 0)); geometry.Add(line); geometry.Add(new Line(new Vector2(-2, 1), new Vector2(-1, 0))); geometry.Add(new Line(new Vector2(2, 1), new Vector2(1, 0))); // Shape looks like \_._/* BoundParticle particle = new BoundParticle(line, false, 0.5, 0, 0); Path path = PathFinding.PathTo(particle, new Vector2(2, 1), geometry, 0.1f, -0.2f); Assert.IsNotNull(path); }
public void TestBowlSlowdown() { GeometryCollection geometry = new GeometryCollection(); Line line = new Line(new Vector2(1, 0), new Vector2(-1, 0)); geometry.Add(line); geometry.Add(new Line(new Vector2(-2, 1), new Vector2(-1, 0))); geometry.Add(new Line(new Vector2(2, 1), new Vector2(1, 0))); // Shape looks like \_._/* BoundParticle particle = new BoundParticle(line, false, 0.5, 0, 0); Path path = PathFinding.PathTo(particle, new Vector2(2, 1), geometry, 0.1f, -0.1f); // Exact answer: √(80+120√2)-√40 AssertExtra.AreApproximate(9.4775213623, path.totalTime); }
public static Path PathTo(BoundParticle start, Point end, GeometryCollection geometry, float accel, float gravity) { // construct a new critical point Point newPoint = start.position(); Line line1 = new Line(newPoint, start.boundTo.p1); Line line2 = new Line(newPoint, start.boundTo.p2); geometry.Add(line1); geometry.Add(line2); geometry.Remove(start.boundTo); Queue<PathCriticalPoint> bfs = new Queue<PathCriticalPoint>(); // map time function for two attached points Dictionary<PathCriticalPoint, PointFunction> dict = new Dictionary<PathCriticalPoint, PointFunction>(); PathCriticalPoint state1 = new PathCriticalPoint(line1, true); PathCriticalPoint state2 = new PathCriticalPoint(line2, true); dict[state1] = new PointFunction(0); dict[state2] = new PointFunction(0); bfs.Enqueue(state1); bfs.Enqueue(state2); // bfs will simply search through steps, and will not continue a branch if it doesn't improve anything while (bfs.Count > 0) { var head = bfs.Dequeue(); // TODO: currently not going back to previously visited points, as the logic isn't correct for it var currPoint = head.firstPoint ? head.line.p1 : head.line.p2; foreach (var nextLine in geometry.LinesAttachedTo(currPoint).Where(l => !l.Equals(head.line))) { var nextPoint = (nextLine.p1.Equals(currPoint)) ? nextLine.p2 : nextLine.p1; var nextState = new PathCriticalPoint(nextLine, nextLine.p1.Equals(nextPoint)); PointFunction timings = new PointFunction(currPoint, nextPoint, dict[head], accel, gravity); WorkWithTimings(end, geometry, bfs, dict, nextState, timings); // also attempt to return along that path var nextState2 = new PathCriticalPoint(nextLine, nextLine.p1.Equals(currPoint)); PointFunction timings2 = PointFunction.Return(currPoint, nextPoint, dict[head], accel, gravity); WorkWithTimings(end, geometry, bfs, dict, nextState2, timings2); } } Path answer = null; if (ContainsAnyState(dict, end, geometry, 0)) { answer = BestOfAnyState(dict, end, geometry, 0); } geometry.Remove(newPoint); geometry.Add(start.boundTo); return answer; }
public BoundParticle FirstCollision(Particle particle) { var v = particle.velocity; var p = particle.position; if (v.X == 0 && v.Y == 0) return null; BoundParticle collidedWith = null; double minT = Double.MaxValue; foreach (Line line in lines) { // calculate t, the amount of time it would take to collide // p+v*t=line.p1+(line.p2-line.p1)*g // p.x+v.x*t=line.p1.x+(line.p2.x-line.p1.x)*g // p.y+v.y*t=line.p1.y+(line.p2.y-line.p1.y)*g // (line.p1.x+(line.p2.x-line.p1.x)*g)*v.y-(line.p1.y+(line.p2.y-line.p1.y)*g)*v.x=p.x*v.y-p.y*v*x // g=(p.x*v.y-p.y*v.x-v.y*line.p1.x+line.p1.y*v.x)/((line.p2.x-line.p1.x)*v.y-(line.p2.y-line.p1.y)*v.x) double g = (p.X*v.Y-p.Y*v.X-v.Y*line.p1.v.Position.X+line.p1.v.Position.Y*v.X)/((line.p2.v.Position.X-line.p1.v.Position.X)*v.Y-(line.p2.v.Position.Y-line.p1.v.Position.Y)*v.X); double t = (line.p1.v.Position.Y + (line.p2.v.Position.Y - line.p1.v.Position.Y) * g - p.Y) / v.Y; if(t>=0 && g>=0 && g<=1 && t<minT && t<=1) { minT = t; double gv = Vector2.Dot(v, (Vector2)line.p2 - line.p1) / (((Vector2)line.p2 - line.p1).LengthSquared()); double ga = Vector2.Dot(particle.gravity, line) / ((Vector2)line).LengthSquared(); collidedWith = new BoundParticle(line, v.CrossProduct(line) > 0, g, gv, ga); } } return collidedWith; }
internal void Update() { mouseListener.Update(); var keystate = Keyboard.GetState(); if(keystate.IsKeyDown(Keys.Z) && !playback && isBound) // TODO: we can improve this to work even when unbound { var target = geometry.SnapToClosePoint(mouseListener.Transform(Mouse.GetState())); recording = PathFinding.PathTo(player2, target, geometry, ACCEL, GRAVITY); if (recording != null) { timeInRecording = 0; recordingPointer = new Particle(recording.absolutePosAt(0), Vector2.Zero, Vector2.Zero); playback = true; } } if (!playback) { if (!isBound) { var collision = geometry.FirstCollision(player); if (collision != null) { isBound = true; player2 = collision; } } if (isBound) { bool upsideDown = (((Vector2)player2.boundTo).X < 0) != player2.onCW; if (upsideDown) { isBound = false; player.position = player2.boundTo.p1 + Vector2.Multiply(player2.boundTo, (float)player2.g); player.velocity = Vector2.Multiply(player2.boundTo, (float)player2.gv) + player.gravity; player.position += player.velocity; player.velocity += player.gravity; } else { player2.g += player2.gv; player2.gv += player2.ga; if (keystate.IsKeyDown(Keys.A)) { player2.gv += ACCEL * ((player2.onCW) ? 1 : -1) / ((Vector2)player2.boundTo).Length(); } if (keystate.IsKeyDown(Keys.D)) { player2.gv -= ACCEL * ((player2.onCW) ? 1 : -1) / ((Vector2)player2.boundTo).Length(); } if (player2.g > 1 || player2.g < 0) { var exitingPoint = (player2.g < 0) ? player2.boundTo.p1 : player2.boundTo.p2; var nextList = geometry.LinesAttachedTo(exitingPoint).Where(l => l != player2.boundTo); nextList = nextList.Where(l => (((((Vector2)player2.boundTo).CrossProduct(l) < 0) != player2.onCW) != (player2.boundTo.p1 == exitingPoint)) != ((exitingPoint == l.p1) == (exitingPoint == player2.boundTo.p1))); if (nextList.Count() > 0) { var next = nextList.OrderBy(l => Math.Abs(((Vector2)player2.boundTo).CrossProduct(l)) / ((Vector2)l).Length()).Last(); var multiplier = Vector2.Dot(next, player2.boundTo) / (((Vector2)next).Length() * ((Vector2)player2.boundTo).Length()) * ((Vector2)player2.boundTo).Length() / ((Vector2)next).Length(); if ((exitingPoint == next.p1) == (exitingPoint == player2.boundTo.p1)) { player2.onCW = !player2.onCW; } player2.boundTo = next; player2.g = (next.p1 == exitingPoint) ? 0 : 1; player2.gv *= multiplier; player2.ga = Vector2.Dot(player.gravity, (Vector2)next) / (((Vector2)next).LengthSquared()); } else { isBound = false; player.position = player2.boundTo.p1 + Vector2.Multiply(player2.boundTo, (float)player2.g); player.velocity = Vector2.Multiply(player2.boundTo, (float)player2.gv); } } } } else { // order of this matters player.position += player.velocity; player.velocity += player.gravity; } } }
public void TestMultipleFlatLines() { int n = 10; // half the total number of points GeometryCollection geometry = new GeometryCollection(); Line line = new Line(new Vector2(-1, 0), new Vector2(1, 0)); geometry.Add(line); MathExp.Geometry.Point target = null; for(int i=1; i<n; i++) { MathExp.Geometry.Point rightMostPoint = new Vector2(i + 1, 0); Line leftLine = new Line(new Vector2(i, 0), rightMostPoint); Line rightLine = new Line(new Vector2(-i, 0), new Vector2(-i - 1, 0)); geometry.Add(leftLine); geometry.Add(rightLine); target = rightMostPoint; } BoundParticle particle = new BoundParticle(line, false, 0.5, 0, 0); Path path = PathFinding.PathTo(particle, target, geometry, 0.1f, 0.1f); AssertExtra.AreApproximate(path.totalTime, 2 * Math.Sqrt(10 * n)); for (float t = 0; t < path.totalTime; t += 0.01f) { Vector2 pos = path.absolutePosAt(t); AssertExtra.AreApproximate(0, pos.Y); if (t < Math.Sqrt(10*n)) { // when speeding up to approach AssertExtra.AreApproximate(0.1 * t * t / 2, pos.X); } else { // when slowing down to stop double g = t - Math.Sqrt(10*n); AssertExtra.AreApproximate(-0.1 * g * g / 2 + Math.Sqrt(10*n) * 0.1 * g + 0.5*n, pos.X); } } }
public void TestSingleFlatLine() { GeometryCollection geometry = new GeometryCollection(); Line line = new Line(new Vector2(-1, 0), new Vector2(1, 0)); geometry.Add(line); BoundParticle particle = new BoundParticle(line, false, 0.5, 0, 0); Path path = PathFinding.PathTo(particle, new Vector2(1, 0), geometry, 0.1f, 0.1f); AssertExtra.AreApproximate(2 * Math.Sqrt(10), path.totalTime); for (float t = 0; t < path.totalTime; t += 0.01f) { Vector2 pos = path.absolutePosAt(t); AssertExtra.AreApproximate(0, pos.Y); if (t < Math.Sqrt(10)) { // when speeding up to approach AssertExtra.AreApproximate(0.1 * t * t / 2, pos.X); }else { // when slowing down to stop double g = t - Math.Sqrt(10); AssertExtra.AreApproximate(-0.1 * g * g / 2 + Math.Sqrt(10)*0.1*g+0.5, pos.X); } } }