public void Rotate() { V2 expected; V2 returned; // --- TEST --- expected = new V2(0.0f, 1.0f); returned = new V2(1.0f, 0.0f); returned.Rotate(2f * (Real)Math.PI * 0.25f); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); // --- TEST --- expected = new V2(-1.0f, 0.0f); returned = new V2(1.0f, 0.0f); returned.Rotate(2f * (Real)Math.PI * 0.5f); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); // --- TEST --- expected = new V2(-1.0f, 0.0f); returned = new V2(0.0f, -1.0f); returned.Rotate(-2f * (Real)Math.PI * 0.25f); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); }
public MaterialInfo( BgfBitmap Texture, string TextureName, string MaterialName, V2 ScrollSpeed) { this.Texture = Texture; this.TextureName = TextureName; this.MaterialName = MaterialName; this.ScrollSpeed = ScrollSpeed; }
/// <summary> /// Constructor using two initial points. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> public BoundingBox2D(V2 P1, V2 P2) { // these initial values make sure // any first point will replace them this.Min.X = Real.MaxValue; this.Min.Y = Real.MaxValue; this.Max.X = Real.MinValue; this.Max.Y = Real.MinValue; ExtendByPoint(P1); ExtendByPoint(P2); }
/// <summary> /// Returns a projection of this instance on another vector. /// </summary> /// <param name="Vector"></param> /// <returns></returns> public V2 GetProjection(V2 Vector) { Real denom = Vector.LengthSquared; if (denom > 0.0f) { Real num = this * Vector; return(Vector * (num / denom)); } else { return(V2.ZERO); } }
public void Scale() { V2 expected; V2 returned; // --- TEST --- expected = new V2(0.0f, 0.0f); returned = new V2(0.0f, 0.0f); returned.Scale(10f); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); // --- TEST --- expected = new V2(10.0f, 0.0f); returned = new V2(1.0f, 0.0f); returned.Scale(10f); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); // --- TEST --- expected = new V2(0.0f, -10.0f); returned = new V2(0.0f, -1.0f); returned.Scale(10f); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); // --- TEST --- expected = new V2(0.0f, 0.0f); returned = new V2(1.34f, -5.563f); returned.Scale(0.0f); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); // --- TEST --- expected = new V2(EPSILON, EPSILON); returned = new V2(1.0f, 1.0f); returned.Scale(EPSILON); Assert.AreEqual(expected.X, returned.X, EPSILON); Assert.AreEqual(expected.Y, returned.Y, EPSILON); }
/// <summary> /// Converts V2 direction to radian angle. /// </summary> /// <param name="Direction">Direction vector</param> /// <returns>Value in [0..2PI]</returns> public static Real GetRadianForDirection(ref V2 Direction) { // normalize Direction.Normalize(); // get angle between x-axis and direction in radian. Real cosphi = Direction * V2.UNITX; Real phi = (Real)Math.Acos(cosphi); // phi is always in [0..PI], not in [0..2PI] // this increases to full range [0..2PI] if (Direction.Y < 0) { phi = GeometryConstants.TWOPI - phi; } return(phi); }
/// <summary> /// Returns the side (-1 or 1) of this (point) instance /// for the line given by points P1 and P2. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <returns></returns> public int GetSide(V2 P1, V2 P2) { Real val = (P2.X - P1.X) * (Y - P1.Y) - (P2.Y - P1.Y) * (X - P1.X); if (val >= EPSILON) { return(1); } else if (val <= -EPSILON) { return(-1); } else { return(0); } }
/// <summary> /// Returns a random point within a 2D triangle. /// </summary> /// <remarks> /// See: /// http://math.stackexchange.com/questions/18686/uniform-random-point-in-triangle /// </remarks> /// <param name="A"></param> /// <param name="B"></param> /// <param name="C"></param> /// <returns>Random point</returns> public static V2 RandomPointInTriangle(ref V2 A, ref V2 B, ref V2 C) { // create two randoms within [0,1] Real rnd1 = (Real)Random.NextDouble(); Real rnd2 = (Real)Random.NextDouble(); // get rootsqrt Real sqrt_rnd1 = (Real)Math.Sqrt(rnd1); // coefficients Real coeff1 = 1 - sqrt_rnd1; Real coeff2 = sqrt_rnd1 * (1 - rnd2); Real coeff3 = rnd2 * sqrt_rnd1; // return random point return(new V2( coeff1 * A.X + coeff2 * B.X + coeff3 * C.X, coeff1 * A.Y + coeff2 * B.Y + coeff3 * C.Y)); }
/// <summary> /// Adjusts the current min, max accordingly in case /// the new point lies outside the current box. /// </summary> /// <param name="Point"></param> public void ExtendByPoint(V2 Point) { if (Point.X < Min.X) { Min.X = Point.X; } if (Point.X > Max.X) { Max.X = Point.X; } if (Point.Y < Min.Y) { Min.Y = Point.Y; } if (Point.Y > Max.Y) { Max.Y = Point.Y; } }
/// <summary> /// Tests whether this V2 instance lies on a line segment /// given by points P1 and P2. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <returns></returns> public bool IsOnLineSegment(V2 P1, V2 P2) { // the point is not even on the infinite line given by P1P2 if (GetSide(P1, P2) != 0) { return(false); } // if on infinite line, must also be in boundingbox V2 min, max; min.X = Math.Min(P1.X, P2.X); min.Y = Math.Min(P1.Y, P2.Y); max.X = Math.Max(P1.X, P2.X); max.Y = Math.Max(P1.Y, P2.Y); return (min.X <= X && X <= max.X && min.Y <= Y && Y <= max.Y); }
/// <summary> /// Calculates the FIRST (entry) intersection point of a line and a circle. /// </summary> /// <remarks> /// http://stackoverflow.com/questions/1073336/circle-line-collision-detection /// </remarks> /// <param name="LineStart"></param> /// <param name="LineEnd"></param> /// <param name="CircleCenter"></param> /// <param name="Radius"></param> /// <param name="Intersect"></param> /// <returns>True if interesction, false if none.</returns> public static bool IntersectLineCircle(ref V2 LineStart, ref V2 LineEnd, ref V2 CircleCenter, Real Radius, out V2 Intersect) { // default output param Intersect.X = 0; Intersect.Y = 0; // returnval bool intersects = false; V2 d = LineEnd - LineStart; V2 f = LineStart - CircleCenter; Real a = d * d; Real b = 2 * (f * d); Real c = (f * f) - (Radius * Radius); Real discriminant = b * b - 4 * a * c; if (discriminant >= 0) { discriminant = (Real)Math.Sqrt(discriminant); Real t1 = (-b - discriminant) / (2 * a); Real t2 = (-b + discriminant) / (2 * a); if (t1 >= 0 && t1 <= 1) { intersects = true; Intersect = LineStart + (t1 * d); } else if (t2 >= 0 && t2 <= 1) { intersects = true; Intersect = LineStart + (t2 * d); } } return(intersects); }
/// <summary> /// True if infinite lines represented by P1P2 and Q1Q2 intersect. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <param name="Q1"></param> /// <param name="Q2"></param> /// <param name="Intersect"></param> /// <returns></returns> public static bool IntersectInfiniteLines(ref V2 P1, ref V2 P2, ref V2 Q1, ref V2 Q2, out V2 Intersect) { // variant 2 Real denom = (P1.X - P2.X) * (Q1.Y - Q2.Y) - (P1.Y - P2.Y) * (Q1.X - Q2.X); // parallel if (denom > -0.001f && denom < 0.001f) { Intersect.X = 0.0f; Intersect.Y = 0.0f; return(false); } Real num; num = (P1.X * P2.Y - P1.Y * P2.X) * (Q1.X - Q2.X) - (P1.X - P2.X) * (Q1.X * Q2.Y - Q1.Y * Q2.X); Intersect.X = num / denom; num = (P1.X * P2.Y - P1.Y * P2.X) * (Q1.Y - Q2.Y) - (P1.Y - P2.Y) * (Q1.X * Q2.Y - Q1.Y * Q2.X); Intersect.Y = num / denom; return(true); }
/// <summary> /// Checks for intersection of finite line segment and a circle. /// Returns true if there is an intersection, or if line is fully inside circle. /// </summary> /// <remarks> /// http://stackoverflow.com/questions/1073336/circle-line-collision-detection /// </remarks> /// <param name="LineStart"></param> /// <param name="LineEnd"></param> /// <param name="CircleCenter"></param> /// <param name="Radius"></param> /// <returns>True if interesction, false if none.</returns> public static bool IntersectOrInsideLineCircle(ref V2 LineStart, ref V2 LineEnd, ref V2 CircleCenter, Real Radius) { V2 d = LineEnd - LineStart; V2 f = LineStart - CircleCenter; Real a = d * d; Real b = 2 * (f * d); Real c = (f * f) - (Radius * Radius); Real discriminant = b * b - 4 * a * c; if (discriminant <= 0 || a == 0) { return(false); } discriminant = (Real)Math.Sqrt(discriminant); Real t1 = (-b - discriminant) / (2 * a); Real t2 = (-b + discriminant) / (2 * a); return((t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1) || (t1 <= 0 && t2 >= 1)); }
/// <summary> /// Checks two finite line segments for intersection. /// </summary> /// <param name="P1">First point of first line segment</param> /// <param name="P2">Second point of first line segment</param> /// <param name="Q1">First point of second line segment</param> /// <param name="Q2">Second point of second line segment</param> /// <param name="Intersect">Intersection point</param> /// <returns>True if interesction, false if none.</returns> public static bool IntersectLineLine(V2 P1, V2 P2, V2 Q1, V2 Q2, out V2 Intersect) { Intersect.X = 0.0f; Intersect.Y = 0.0f; V2 b = P2 - P1; V2 d = Q2 - Q1; Real bDotDPerp = b.X * d.Y - b.Y * d.X; // if b dot d == 0, it means the lines are parallel/collapse // so have infinite intersection points or none if (bDotDPerp == 0) { return(false); } V2 c = Q1 - P1; Real t = (c.X * d.Y - c.Y * d.X) / bDotDPerp; if (t < 0.0f || t > 1.0f) { return(false); } Real u = (c.X * b.Y - c.Y * b.X) / bDotDPerp; if (u < 0.0f || u > 1.0f) { return(false); } b.Scale(t); Intersect = P1 + b; return(true); }
protected void DrawWall(Graphics G, RooWall Wall, Pen Pen, bool Infinite, Pen PenInfinite) { V2 p1p2; // transform points to match world of pixeldrawing float transx1 = (float)(Wall.P1.X - boxMin.X) * ZoomInv; float transy1 = (float)(Wall.P1.Y - boxMin.Y) * ZoomInv; float transx2 = (float)(Wall.P2.X - boxMin.X) * ZoomInv; float transy2 = (float)(Wall.P2.Y - boxMin.Y) * ZoomInv; // draw extensions of line if (Infinite) { V2 p1 = new V2(transx1, transy1); V2 p2 = new V2(transx2, transy2); p1p2 = p2 - p1; p2 += 1000f * p1p2; p1 -= 1000f * p1p2; G.DrawLine(PenInfinite, (float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y); } // check if this is an issue line (almost horizontal or vertical, but not fully) p1p2 = Wall.GetP1P2(); if (chkVertHortLines.Checked && p1p2.X != 0.0f && p1p2.Y != 0.0f) { Real m = (Real)(p1p2.Y / p1p2.X); if ((m > -0.125f && m < 0.125f) || (m > 8.0f || m < -8.0f)) Pen = penRed2; } // draw line G.DrawLine(Pen, transx1, transy1, transx2, transy2); }
public void MinDistanceToLineSegment() { Real expected; Real returned; V2 s; V2 p1, p2; // --- TEST --- // p1=p2=s=0 s = new V2(0.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(0.0f, 0.0f); expected = 0.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s on p1 s = new V2(0.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 0.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s on p2 s = new V2(1.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 0.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s in mid of p1p2 s = new V2(0.0f, 0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 0.5f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // p1=p2 s = new V2(0.0f, 0.0f); p1 = new V2(1.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 1.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s on extension p1p2, closest p2 s = new V2(2.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 1.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s on extension p2p1, closest p1 s = new V2(-1.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 1.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s perpendicular, mid p1p2 closest s = new V2(0.5f, 0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 0.5f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s perpendicular, p1 closest + s = new V2(0.0f, 1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 1.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s perpendicular, p1 closest - s = new V2(0.0f, -1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 1.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s perpendicular, p2 closest + s = new V2(1.0f, 1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 1.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s perpendicular, p2 closest - s = new V2(1.0f, -1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = 1.0f; returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s 45° to p2 closest + s = new V2(2.0f, 1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = (Real)Math.Sqrt(2.0f); returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); // --- TEST --- // s 45° to p2 closest - s = new V2(2.0f, -1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 0.0f); expected = (Real)Math.Sqrt(2.0f); returned = s.MinDistanceToLineSegment(p1, p2); Assert.AreEqual(expected, returned, EPSILON); }
/// <summary> /// Checks a finite line segment for intersection with infinite line. /// </summary> /// <param name="P1">First point of finite line segment</param> /// <param name="P2">Second point of finite line segment</param> /// <param name="Q1">First point in infinte line</param> /// <param name="Q2">Second point in infinite line</param> /// <param name="Intersect"></param> /// <returns></returns> public static LineInfiniteLineIntersectionType IntersectLineInfiniteLine(V2 P1, V2 P2, V2 Q1, V2 Q2, out V2 Intersect) { Intersect.X = 0.0f; Intersect.Y = 0.0f; /*****************************************************************/ int sideP1 = P1.GetSide(Q1, Q2); int sideP2 = P2.GetSide(Q1, Q2); // case (a): no intersection // both finite line endpoints are on the same side of the infinite line if (sideP1 == sideP2 && sideP1 != 0) { return(LineInfiniteLineIntersectionType.NoIntersection); } // case (b): real intersection (not only touch) // both finite line endpoints are on different sides if (sideP1 != sideP2 && sideP1 != 0 && sideP2 != 0) { // variant 2 Real denom = (P1.X - P2.X) * (Q1.Y - Q2.Y) - (P1.Y - P2.Y) * (Q1.X - Q2.X); Real num; num = (P1.X * P2.Y - P1.Y * P2.X) * (Q1.X - Q2.X) - (P1.X - P2.X) * (Q1.X * Q2.Y - Q1.Y * Q2.X); Intersect.X = num / denom; num = (P1.X * P2.Y - P1.Y * P2.X) * (Q1.Y - Q2.Y) - (P1.Y - P2.Y) * (Q1.X * Q2.Y - Q1.Y * Q2.X); Intersect.Y = num / denom; return(LineInfiniteLineIntersectionType.OneIntersection); } // case (c): fully coincide // both finite line endpoints are on the infinite line if (sideP1 == 0 && sideP2 == 0) { Intersect.X = P1.X; Intersect.Y = P1.Y; return(LineInfiniteLineIntersectionType.FullyCoincide); } // case (d): // finite line endpoint P1 touches infinite line if (sideP1 != sideP2 && sideP1 == 0) { Intersect.X = P1.X; Intersect.Y = P1.Y; return(LineInfiniteLineIntersectionType.OneBoundaryPoint); } // case (d): // finite line endpoint P2 touches infinite line if (sideP1 != sideP2 && sideP2 == 0) { Intersect.X = P2.X; Intersect.Y = P2.Y; return(LineInfiniteLineIntersectionType.OneBoundaryPoint); } // huh? something wrong? throw new Exception("Unknown intersection"); }
/// <summary> /// Returns the scalar crossproduct of this vector /// and the parameter vector. /// </summary> /// <remarks> /// http://stackoverflow.com/questions/243945/calculating-a-2d-vectors-cross-product /// </remarks> /// <param name="v"></param> /// <returns></returns> public Real CrossProduct(V2 v) { return((X * v.Y) - (Y * v.X)); }
/// <summary> /// See and prefer the variant with ref params! /// </summary> /// <param name="p1"></param> /// <param name="p2"></param> /// <returns></returns> public int GetSide(V2 p1, V2 p2) { return(GetSide(ref p1, ref p2)); }
/// <summary> /// Returns the squared distance to another point /// </summary> /// <param name="Destination"></param> /// <returns></returns> public Real DistanceToSquared(V2 Destination) { V2 diff = Destination - this; return(diff.LengthSquared); }
protected void HandleMove(MoveMessage Message) { RoomObject roomObject = RoomObjects.GetItemByID(Message.ObjectID); if (roomObject != null) { // create destination from values V2 destination = new V2(Message.NewCoordinateX, Message.NewCoordinateY); // initiate movement roomObject.StartMoveTo(destination, (byte)Message.MovementSpeed); } }
/// <summary> /// Update Viewer dependent properties on RoomObjects /// based on ViewerPosition in DataController /// </summary> protected void ProcessViewerAngle() { // get 2D position of viewer V2 position = new V2(ViewerPosition.X, ViewerPosition.Z); // update roomobjects foreach (RoomObject obj in RoomObjects) obj.UpdateViewerAngle(position); // update projectiles foreach (Projectile obj in Projectiles) obj.UpdateViewerAngle(position); }
/// <summary> /// Checks if this wall blocks a /// move between Start and End /// </summary> /// <param name="Start">A 3D location</param> /// <param name="End">A 2D location</param> /// <param name="PlayerHeight">Height of the player for ceiling collisions</param> /// <returns></returns> public bool IsBlockingMove(V3 Start, V2 End, Real PlayerHeight) { // get distance of end to finite line segment Real distEnd = End.MinSquaredDistanceToLineSegment(P1, P2); // end is far enough away, no block if (distEnd >= GeometryConstants.WALLMINDISTANCE2) return false; /*************************************************************************/ // end is too 'too' close to wall V2 start2D = new V2(Start.X, Start.Z); int startside = start2D.GetSide(P1, P2); int endside = End.GetSide(P1, P2); Real endheight; ushort bgfbmp; /*************************************************************************/ // allow moving away from wall if (startside == endside) { Real distStart = start2D.MinSquaredDistanceToLineSegment(P1, P2); if (distEnd > distStart) return false; } /*************************************************************************/ // prevent moving through non-passable side if ((startside < 0 && LeftSide != null && !LeftSide.Flags.IsPassable) || (startside > 0 && RightSide != null && !RightSide.Flags.IsPassable)) return true; /*************************************************************************/ // check step-height endheight = 0.0f; if (startside >= 0) { endheight = (LeftSector != null) ? LeftSector.CalculateFloorHeight(End.X, End.Y, true) : 0.0f; bgfbmp = (RightSide != null) ? RightSide.LowerTexture : (ushort)0; } else { endheight = (RightSector != null) ? RightSector.CalculateFloorHeight(End.X, End.Y, true) : 0.0f; bgfbmp = (LeftSide != null) ? LeftSide.LowerTexture : (ushort)0; } if (bgfbmp > 0 && (endheight - Start.Y > GeometryConstants.MAXSTEPHEIGHT)) return true; /*************************************************************************/ // check head collision with ceiling endheight = 0.0f; if (startside >= 0) { endheight = (LeftSector != null) ? LeftSector.CalculateCeilingHeight(End.X, End.Y) : 0.0f; bgfbmp = (RightSide != null) ? RightSide.UpperTexture : (ushort)0; } else { endheight = (RightSector != null) ? RightSector.CalculateCeilingHeight(End.X, End.Y) : 0.0f; bgfbmp = (LeftSide != null) ? LeftSide.UpperTexture : (ushort)0; } if (bgfbmp > 0 && (endheight < Start.Y + PlayerHeight)) return true; /*************************************************************************/ return false; }
public void GetSide() { int expected; int returned; V2 s; V2 p1, p2; /**************************************************/ // --- TEST --- // p1=p2=s=0, no line s = new V2(0.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(0.0f, 0.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // p1=s s = new V2(0.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(0.0f, 1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // p2=s s = new V2(0.0f, 1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(0.0f, 1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); /**************************************************/ // --- TEST --- // s is somewhere on line s = new V2(0.5f, 0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is somewhere on line s = new V2(-0.5f, -0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(-1.0f, -1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is somewhere on line s = new V2(-0.5f, 0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(-1.0f, 1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is somewhere on line s = new V2(0.5f, -0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, -1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); /**************************************************/ // --- TEST --- // s is not on line segment, but on same infinite line s = new V2(2.0f, 2.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is not on line segment, but on same infinite line s = new V2(-2.0f, -2.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is not on line segment, but very far on same infinite line s = new V2(-2000000.0f, -2000000.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is left of line, but close (in bbox) s = new V2(0.45f, 0.55f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = 1; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is left of line and far away (outside bbox) s = new V2(0.0f, 1000.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = 1; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is on line s = new V2(14304f, 32512f); p1 = new V2(15872f, 32512f); p2 = new V2(3433.6f, 32512f); expected = 0; returned = s.GetSide(p1, p2); Assert.AreEqual(expected, returned); }
/// <summary> /// Adjusts the current min, max accordingly in case /// the new point lies outside the current box. /// </summary> /// <param name="Point"></param> public void ExtendByPoint(V2 Point) { if (Point.X < Min.X) Min.X = Point.X; if (Point.X > Max.X) Max.X = Point.X; if (Point.Y < Min.Y) Min.Y = Point.Y; if (Point.Y > Max.Y) Max.Y = Point.Y; }
/// <summary> /// Typed equals /// </summary> /// <param name="obj"></param> /// <returns></returns> public bool Equals(V2 obj) { return (Math.Abs(obj.X - X) <= EPSILON && Math.Abs(obj.Y - Y) <= EPSILON); }
/// <summary> /// Converts V2 direction to M59 angle. /// </summary> /// <example> /// V2 M59 /// ------------------------- /// (1, 0) = 0 /// (1, 1) = 512 /// (0, 1) = 1024 /// (-1, 1) = 1536 /// (-1, 0) = 2048 /// (-1, -1) = 2560 /// (0, -1) = 3072 /// (1, -1) = 3584 /// (1, -0.0001) = 4096 /// </example> /// <param name="Direction">Direction vector</param> /// <returns>Value in [0..4096]</returns> public static ushort GetAngleForDirection(ref V2 Direction) { return(RadianToBinaryAngle( GetRadianForDirection(ref Direction))); }
/// <summary> /// Returns the distance to another point /// </summary> /// <param name="Destination"></param> /// <returns></returns> public Real DistanceTo(V2 Destination) { V2 diff = Destination - this; return(diff.Length); }
public static List<Tuple<RooSubSector, V2, float>> FindVertexMismatches(V2 Vertex) { const float CLOSE = 2; List<Tuple<RooSubSector, V2, float>> list = new List<Tuple<RooSubSector, V2, float>>(); foreach (RooSubSector s in Room.BSPTreeLeaves) { foreach (V2 v in s.Vertices) { float absdx = (float)Math.Abs(v.X - Vertex.X); float absdy = (float)Math.Abs(v.Y - Vertex.Y); // must be very close but not same pos if (absdx <= CLOSE && absdy <= CLOSE && (absdx > 0 || absdy > 0)) list.Add(new Tuple<RooSubSector, V2, float>(s, v, Math.Max(absdx, absdy))); } } return list; }
/// <summary> /// See and prefer variant with ref params! /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <returns></returns> public bool IsOnLineSegment(V2 P1, V2 P2) { return(IsOnLineSegment(ref P1, ref P2)); }
protected void Calculate( ObjectBase Data, BgfBitmap MainFrame, bool UseViewerFrame = true, bool ApplyYOffset = true, byte RootHotspotIndex = 0, Real Quality = DEFAULTQUALITY, bool ScalePow2 = false, uint Width = 0, uint Height = 0, bool CenterVertical = false, bool CenterHorizontal = false) { BgfBitmap mainFrame = MainFrame; BgfFile mainResource = Data.Resource; byte mainColor = Data.ColorTranslation; BgfBitmap subOvFrame; BgfBitmapHotspot subOvHotspot; SubOverlay subOvParent; BgfBitmap subOvParentFrame; BgfBitmapHotspot subOvParentHotspot; bool rootSpotFound = false; // use custom compose root (suboverlay=mainoverlay) // if it's not found, fallback to full compose if (RootHotspotIndex > 0) { SubOverlay subOv = Data.GetSubOverlayByHotspot(RootHotspotIndex); if (subOv != null) { rootSpotFound = true; if (UseViewerFrame) mainFrame = subOv.ViewerFrame; else mainFrame = subOv.FrontFrame; mainResource = subOv.Resource; mainColor = subOv.ColorTranslation; } } if (mainFrame != null && mainResource != null) { Bgf = mainFrame; BgfColor = mainColor; size.X = (Real)mainFrame.Width / (Real)mainResource.ShrinkFactor; size.Y = (Real)mainFrame.Height / (Real)mainResource.ShrinkFactor; origin.X = (Real)mainFrame.XOffset / (Real)mainResource.ShrinkFactor; origin.Y = (Real)mainFrame.YOffset / (Real)mainResource.ShrinkFactor; MaxShrink = mainResource.ShrinkFactor; // used to calculate the boundingbox V2 min = new V2(Origin.X, Origin.Y); V2 max = (ApplyYOffset) ? new V2(Size.X, Size.Y) : new V2(Origin.X + Size.X, Origin.Y + Size.Y); Real x, y; // walk suboverlay structure foreach (SubOverlay subOv in Data.CurrentSubOverlays) { if (UseViewerFrame) { subOvFrame = subOv.ViewerFrame; subOvHotspot = subOv.ViewerHotspot; subOvParent = subOv.ViewerParent; } else { subOvFrame = subOv.FrontFrame; subOvHotspot = subOv.FrontHotspot; subOvParent = subOv.FrontParent; } bool isSubRoot = (subOvParent != null && subOvParent.HotSpot == RootHotspotIndex); if (subOv.Resource != null && subOvFrame != null && subOvHotspot != null && (RootHotspotIndex <= 0 || !rootSpotFound || isSubRoot)) { SubOverlay.RenderInfo subOvInfo = new SubOverlay.RenderInfo(); // save subov & bitmap subOvInfo.SubOverlay = subOv; subOvInfo.Bgf = subOvFrame; // calculate the size of this suboverlay subOvInfo.Size.X = (Real)subOvFrame.Width / (Real)subOv.Resource.ShrinkFactor; subOvInfo.Size.Y = (Real)subOvFrame.Height / (Real)subOv.Resource.ShrinkFactor; // update maxshrink if greater if (subOv.Resource.ShrinkFactor > MaxShrink) MaxShrink = subOv.Resource.ShrinkFactor; // CASE 1: SubOverlay on mainoverlay if (subOvParent == null || isSubRoot) { // calculate the origin of this suboverlay on the mainoverlay subOvInfo.Origin.X = mainFrame.XOffset + ((Real)subOvHotspot.X) + ((Real)subOvFrame.XOffset); subOvInfo.Origin.Y = mainFrame.YOffset + ((Real)subOvHotspot.Y) + ((Real)subOvFrame.YOffset); subOvInfo.Origin.X /= mainResource.ShrinkFactor; subOvInfo.Origin.Y /= mainResource.ShrinkFactor; // determine type of hotspot if (subOvHotspot.Index < 0) subOvInfo.HotspotType = HotSpotType.HOTSPOT_UNDER; else subOvInfo.HotspotType = HotSpotType.HOTSPOT_OVER; } // CASE 2: SubOverlay on SubOverlay on MainOverlay else { if (UseViewerFrame) { subOvParentFrame = subOvParent.ViewerFrame; subOvParentHotspot = subOvParent.ViewerHotspot; } else { subOvParentFrame = subOvParent.FrontFrame; subOvParentHotspot = subOvParent.FrontHotspot; } if (subOvParentHotspot != null && subOvParentFrame != null && subOvParent.Resource != null) { // calculate the origin of this suboverlay on the suboverlay on the mainoverlay subOvInfo.Origin.X = (mainFrame.XOffset + (Real)subOvParentHotspot.X + (Real)subOvParentFrame.XOffset) / (Real)mainResource.ShrinkFactor; subOvInfo.Origin.X += ((Real)subOvHotspot.X + (Real)subOvFrame.XOffset) / (Real)subOvParent.Resource.ShrinkFactor; subOvInfo.Origin.Y = (mainFrame.YOffset + (Real)subOvParentHotspot.Y + (Real)subOvParentFrame.YOffset) / (Real)mainResource.ShrinkFactor; subOvInfo.Origin.Y += ((Real)subOvHotspot.Y + (Real)subOvFrame.YOffset) / (Real)subOvParent.Resource.ShrinkFactor; // determine type of nested hotspot if (subOvParentHotspot.Index > 0 && subOvHotspot.Index > 0) subOvInfo.HotspotType = HotSpotType.HOTSPOT_OVEROVER; else if (subOvParentHotspot.Index > 0 && subOvHotspot.Index < 0) subOvInfo.HotspotType = HotSpotType.HOTSPOT_OVERUNDER; else if (subOvParentHotspot.Index < 0 && subOvHotspot.Index > 0) subOvInfo.HotspotType = HotSpotType.HOTSPOT_UNDEROVER; else if (subOvParentHotspot.Index < 0 && subOvHotspot.Index < 0) subOvInfo.HotspotType = HotSpotType.HOTSPOT_UNDERUNDER; } } // update max boundingbox if (subOvInfo.Origin.X < min.X) min.X = subOvInfo.Origin.X; if (subOvInfo.Origin.Y < min.Y) min.Y = subOvInfo.Origin.Y; x = subOvInfo.Origin.X + subOvInfo.Size.X; y = subOvInfo.Origin.Y + subOvInfo.Size.Y; if (x > max.X) max.X = x; if (y > max.Y) max.Y = y; // save info for this suboverlay SubBgf.Add(subOvInfo); } } // get dimension from boundingbox dimension.X = Math.Abs(max.X - min.X); dimension.Y = Math.Abs(max.Y - min.Y); // move all origins so minimum hits 0/0 // preparation for drawing (pixel origin is 0/0) Translate(-min); // get the center of the dimension box // this is also the center of our image after the translate above V2 bbCenter = dimension * 0.5f; // get the center of the main overlay V2 mainOriginCenter = Origin + (Size * 0.5f); // move the x center of the main overlay to the x center of dimensionbox V2 centerMove = new V2(bbCenter.X - mainOriginCenter.X, 0.0f); Translate(centerMove); // since this moves a part outside the dimension box // we need to add this size of the move to the dimension, // to the right AND left side, so our centering above stays centered // then we remove to the center. V2 center = new V2(Math.Abs(centerMove.X), 0.0f); dimension.X += center.X * 2.0f; Translate(center); // scale so highest resolution resource has 1:1 ratio (no downscale) // and apply custom quality level Scale((Real)MaxShrink); // scale up to pow2 if set if (ScalePow2) { Real maxQuality = Quality * QUALITYBASE; Real ratioX = maxQuality / dimension.X; Real ratioY = maxQuality / dimension.Y; if (ratioX <= ratioY && ratioX < 1.0f) Scale(ratioX); else if (ratioX > ratioY && ratioY < 1.0f) Scale(ratioY); // get next power of 2 size V2 pow2Size = new V2( MathUtil.NextPowerOf2((uint)dimension.X), MathUtil.NextPowerOf2((uint)dimension.Y)); // scale so we use at least all pixels either from upscaled width or height ScaleToBox(pow2Size, CenterHorizontal, CenterVertical); } else if (Width > 0 && Height > 0) { // use user given size V2 userSize = new V2(Width, Height); // scale so we use at least all pixels either from upscaled width or height ScaleToBox(userSize, CenterHorizontal, CenterVertical); } } }
/// <summary> /// Returns the side (-1 or 1) of this (point) instance /// for the line given by points P1 and P2. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <returns></returns> public int GetSide(V2 P1, V2 P2) { return(Math.Sign((P2.X - P1.X) * (Y - P1.Y) - (P2.Y - P1.Y) * (X - P1.X))); }
/// <summary> /// Moves the origins by given translation. /// </summary> /// <param name="Translation"></param> protected void Translate(V2 Translation) { origin.X += Translation.X; origin.Y += Translation.Y; foreach (SubOverlay.RenderInfo subInfo in SubBgf) { subInfo.Origin.X += Translation.X; subInfo.Origin.Y += Translation.Y; } }
/// <summary> /// Typed equals /// </summary> /// <param name="obj"></param> /// <returns></returns> public bool Equals(V2 obj) { return(obj.X == X && obj.Y == Y); }
/// <summary> /// Modifies a 2D vector to slide along this wall /// Based on projection. /// </summary> /// <param name="Begin"></param> /// <param name="End"></param> /// <returns></returns> public V2 SlideAlong(V2 Begin, V2 End) { V2 diff = End - Begin; V2 projection = diff.GetProjection(GetP1P2()); return Begin + projection; }
/// <summary> /// Checks two finite line segments for intersection. /// </summary> /// <param name="P1">First point of first line segment</param> /// <param name="P2">Second point of first line segment</param> /// <param name="Q1">First point of second line segment</param> /// <param name="Q2">Second point of second line segment</param> /// <param name="Intersect">Intersection point</param> /// <returns>LineLineIntersectionType</returns> public static LineLineIntersectionType IntersectLineLine(ref V2 P1, ref V2 P2, ref V2 Q1, ref V2 Q2, out V2 Intersect) { Intersect.X = 0.0f; Intersect.Y = 0.0f; /*****************************************************************/ // fully coincide, lines are equal if ((P1 == Q1 && P2 == Q2) || (P1 == Q2 && P2 == Q1)) { // use one point as intersection Intersect.X = P1.X; Intersect.Y = P1.Y; return(LineLineIntersectionType.FullyCoincide); } /*****************************************************************/ V2 b = P2 - P1; V2 d = Q2 - Q1; Real bDotDPerp = b.X * d.Y - b.Y * d.X; /****************************************************************** * SPECIAL CASE: b dot d == 0 * ******************************************************************/ // three subcases: // (a) no intersect: parallel or on same infinite line but don't overlap // (b) partially coincide with many intersections // (c) touch each other at one endpoint if (bDotDPerp == 0.0f) { bool isP1onQ1Q2 = P1.IsOnLineSegment(ref Q1, ref Q2); bool isP2onQ1Q2 = P2.IsOnLineSegment(ref Q1, ref Q2); bool isQ1onP1P2 = Q1.IsOnLineSegment(ref P1, ref P2); bool isQ2onP1P2 = Q2.IsOnLineSegment(ref P1, ref P2); // subcase (a): p1p2 and q1q2 don't share a point if (!isP1onQ1Q2 && !isP2onQ1Q2 && !isQ1onP1P2 && !isQ2onP1P2) { return(LineLineIntersectionType.NoIntersection); } // subcase (b): P1P2 fully inside Q1Q2 if (isP1onQ1Q2 && isP2onQ1Q2) { // use p1 for intersection Intersect.X = P1.X; Intersect.Y = P1.Y; return(LineLineIntersectionType.PartiallyCoincide); } // subcase (b): Q1Q2 fully inside P1P2 if (isQ1onP1P2 && isQ2onP1P2) { // use p1 for intersection Intersect.X = Q1.X; Intersect.Y = Q1.Y; return(LineLineIntersectionType.PartiallyCoincide); } // subcase (c): touch at P1 if (isP1onQ1Q2 && !isP2onQ1Q2) { // use p1 for intersection Intersect.X = P1.X; Intersect.Y = P1.Y; return(LineLineIntersectionType.OneBoundaryPoint); } // subcase (c): touch at P2 if (!isP1onQ1Q2 && isP2onQ1Q2) { // use p1 for intersection Intersect.X = P2.X; Intersect.Y = P2.Y; return(LineLineIntersectionType.OneBoundaryPoint); } // subcase (c): touch at Q1 if (isQ1onP1P2 && !isQ2onP1P2) { // use p1 for intersection Intersect.X = Q1.X; Intersect.Y = Q1.Y; return(LineLineIntersectionType.OneBoundaryPoint); } // subcase (c): touch at Q2 if (!isQ1onP1P2 && isQ2onP1P2) { // use p1 for intersection Intersect.X = Q2.X; Intersect.Y = Q2.Y; return(LineLineIntersectionType.OneBoundaryPoint); } } /****************************************************************** * DEFAULT CASE: INFINITE LINES CROSS * ******************************************************************/ V2 c = Q1 - P1; Real t = (c.X * d.Y - c.Y * d.X) / bDotDPerp; if (t < 0.0f || t > 1.0f) { return(LineLineIntersectionType.NoIntersection); } Real u = (c.X * b.Y - c.Y * b.X) / bDotDPerp; if (u < 0.0f || u > 1.0f) { return(LineLineIntersectionType.NoIntersection); } // -- finite line segments cross or boundary point! -- b.Scale(t); Intersect = P1 + b; // if the intersection point is also the endpoint of // one of the finite lines, it's a boundary point if (Intersect == P1 || Intersect == P2 || Intersect == Q1 || Intersect == Q2) { return(LineLineIntersectionType.OneBoundaryPoint); } // true intersection return(LineLineIntersectionType.OneIntersection); }
/// <summary> /// Checks if this wall blocks a /// move between Start and End /// </summary> /// <param name="Start">A 3D location</param> /// <param name="End">A 2D location</param> /// <param name="PlayerHeight">Height of the player for ceiling collisions</param> /// <returns></returns> public bool IsBlocking(V3 Start, V2 End, Real PlayerHeight) { V2 Start2D = new V2(Start.X, Start.Z); // calculate the sides of the points (returns -1, 0 or 1) int startside = Start2D.GetSide(P1, P2); int endside = End.GetSide(P1, P2); // if points are not on same side // the infinite lines cross if (startside != endside) { // verify also the finite line segments cross V2 intersect; LineLineIntersectionType intersecttype = MathUtil.IntersectLineLine(Start2D, End, P1, P2, out intersect); if (intersecttype == LineLineIntersectionType.OneIntersection || intersecttype == LineLineIntersectionType.OneBoundaryPoint || intersecttype == LineLineIntersectionType.FullyCoincide || intersecttype == LineLineIntersectionType.PartiallyCoincide) { // verify the side we've crossed is flaggged as "nonpassable" // if so, we actually have a collision if ((startside < 0 && LeftSide != null && !LeftSide.Flags.IsPassable) || (startside > 0 && RightSide != null && !RightSide.Flags.IsPassable)) { return true; } // still check the stepheight from oldheight to new floor if passable // for too high steps Real endheight = 0.0f; Real diff; if (endside <= 0 && LeftSector != null) endheight = LeftSector.CalculateFloorHeight((int)End.X, (int)End.Y, true); else if (endside > 0 && RightSector != null) endheight = RightSector.CalculateFloorHeight((int)End.X, (int)End.Y, true); diff = endheight - Start.Y; // diff is bigger than max. step height, we have a collision if (diff > GeometryConstants.MAXSTEPHEIGHT) return true; // check the ceiling heights if (endside <= 0 && LeftSector != null) endheight = LeftSector.CalculateCeilingHeight((int)End.X, (int)End.Y); else if (endside > 0 && RightSector != null) endheight = RightSector.CalculateCeilingHeight((int)End.X, (int)End.Y); // diff is bigger than max. step height, we have a collision if (endheight < Start.Y + PlayerHeight) return true; } } return false; }
/// <summary> /// Typed equals /// </summary> /// <param name="obj"></param> /// <returns></returns> public bool Equals(V2 obj) { return Math.Abs(obj.X-X) <= EPSILON && Math.Abs(obj.Y-Y) <= EPSILON; }
public void IsOnLineSegment() { bool expected; bool returned; V2 s, p1, p2; /**************************************************/ // --- TEST --- // p1=p2=s=0, no line s = new V2(0.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(0.0f, 0.0f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // p1=s s = new V2(0.0f, 0.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(0.0f, 1.0f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // p2=s s = new V2(0.0f, 1.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(0.0f, 1.0f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); /**************************************************/ // --- TEST --- // s is somewhere on line s = new V2(0.5f, 0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is somewhere on line s = new V2(-0.5f, -0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(-1.0f, -1.0f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is somewhere on line s = new V2(-0.5f, 0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(-1.0f, 1.0f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is somewhere on line s = new V2(0.5f, -0.5f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, -1.0f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); /**************************************************/ // --- TEST --- // s is not on line segment, but on same infinite line s = new V2(2.0f, 2.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = false; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is not on line segment, but on same infinite line s = new V2(-2.0f, -2.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = false; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is not on line, but close aside of it (in bbox) s = new V2(0.45f, 0.55f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = false; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is not on line and far (outside bbox) s = new V2(0.0f, 1000.0f); p1 = new V2(0.0f, 0.0f); p2 = new V2(1.0f, 1.0f); expected = false; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); // --- TEST --- // s is on line s = new V2(14304f, 32512f); p1 = new V2(15872f, 32512f); p2 = new V2(3433.6f, 32512f); expected = true; returned = s.IsOnLineSegment(p1, p2); Assert.AreEqual(expected, returned); }
/// <summary> /// Returns the distance to another point /// </summary> /// <param name="Destination"></param> /// <returns></returns> public Real DistanceTo(V2 Destination) { V2 diff = Destination - this; return diff.Length; }
/// <summary> /// Returns the squared distance to another point /// </summary> /// <param name="Destination"></param> /// <returns></returns> public Real DistanceToSquared(V2 Destination) { V2 diff = Destination - this; return diff.LengthSquared; }
/// <summary> /// Returns true if P lies inside the boundingbox defines by S and E /// </summary> /// <param name="S"></param> /// <param name="E"></param> /// <param name="P"></param> /// <param name="Epsilon"></param> /// <returns></returns> public static bool IsInBoundingBox(ref V2 S, ref V2 E, ref V2 P, Real Epsilon) { return (Min(S.X, E.X) - Epsilon <= P.X && P.X <= Max(S.X, E.X) + Epsilon && Min(S.Y, E.Y) - Epsilon <= P.Y && P.Y <= Max(S.Y, E.Y) + Epsilon); }
/// <summary> /// Returns the minimum squared distance of this instance to a /// line segment given by points P1, P2. /// Note: line segment != infinite line /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <returns></returns> public Real MinSquaredDistanceToLineSegment(V2 P1, V2 P2) { V2 diff = P2 - P1; Real l2 = diff.LengthSquared; if (l2 == 0.0f) return DistanceToSquared(P1); Real t = ((this - P1) * (P2 - P1)) / l2; if (t < 0.0f) return DistanceToSquared(P1); else if (t > 1.0f) return DistanceToSquared(P2); diff.Scale(t); V2 projection = P1 + diff; return DistanceToSquared(projection); }
/// <summary> /// Recalculates the ViewerAngle property based on a viewer's position. /// </summary> /// <param name="ViewerPosition"></param> public void UpdateViewerAngle(V2 ViewerPosition) { V2 direction = TargetObject.Position2D - Position2D; direction.Normalize(); ushort angle = MathUtil.GetAngleForDirection(direction); // update viewer angle ViewerAngle = MathUtil.GetAngle(ViewerPosition, Position2D, angle); // mark for possible appearance change appearanceChanged = true; }
/// <summary> /// Returns the side (-1 or 1) of this (point) instance /// for the line given by points P1 and P2. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <returns></returns> public int GetSide(V2 P1, V2 P2) { Real val = (P2.X - P1.X) * (Y - P1.Y) - (P2.Y - P1.Y) * (X - P1.X); if (val >= EPSILON) return 1; else if (val <= -EPSILON) return -1; else return 0; }
/// <summary> /// Scales the renderinfo with respect to width/height ratio. /// Only one side will fit the given Dimension exactly afterwards. /// </summary> /// <param name="Dimension"></param> /// <param name="CenterHorizontal"></param> /// <param name="CenterVertical"></param> protected void ScaleToBox(V2 Dimension, bool CenterHorizontal = false, bool CenterVertical = false) { // get scales for both axis Real hScale = Dimension.X / this.Dimension.X; Real vScale = Dimension.Y / this.Dimension.Y; // use the smaller factor so the other side "still fits" // into given dimension if (hScale >= vScale) { // apply scale from y-side Scale(vScale); if (CenterHorizontal) { Real stride = (Dimension.X - this.Dimension.X) / 2.0f; Real strideratio = stride / Dimension.X; Translate(new V2(stride, 0.0f)); uvstart.X = 0.0f + strideratio; uvend.X = 1.0f - strideratio; } else { // adjust UVEnd on x-side uvend.X = 1.0f * (this.Dimension.X / Dimension.X); } } else { // apply scale from x-side Scale(hScale); if (CenterVertical) { Real stride = (Dimension.Y - this.Dimension.Y) / 2.0f; Real strideratio = stride / Dimension.Y; Translate(new V2(0.0f, stride)); uvstart.Y = 0.0f + strideratio; uvend.Y = 1.0f - strideratio; } else { // adjust UVEnd on y-side uvend.Y = 1.0f * (this.Dimension.Y / Dimension.Y); } } this.Dimension = Dimension; }
/// <summary> /// Tests whether this V2 instance lies on a line segment /// given by points P1 and P2. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> /// <returns></returns> public bool IsOnLineSegment(V2 P1, V2 P2) { // the point is not even on the infinite line given by P1P2 if (GetSide(P1, P2) != 0) return false; // if on infinite line, must also be in boundingbox V2 min, max; min.X = Math.Min(P1.X, P2.X); min.Y = Math.Min(P1.Y, P2.Y); max.X = Math.Max(P1.X, P2.X); max.Y = Math.Max(P1.Y, P2.Y); return min.X <= X && X <= max.X && min.Y <= Y && Y <= max.Y; }
protected Real zz3; /* height of top of upper wall */ #endregion Fields #region Constructors /// <summary> /// Constructor by values /// </summary> /// <param name="RooVersion"></param> /// <param name="ServerID">Sometimes also called UserID, used to reference wall by server.</param> /// <param name="RightSideNum">Num of the right side of the wall (1=first, 0=unset)</param> /// <param name="LeftSideNum">Num of the left side of the wall (1=first, 0=unset)</param> /// <param name="P1">2D coordinates of startpoint, must be in 1:1024 units</param> /// <param name="P2">2D coordinates of endpoint, must be in 1:1024 units</param> /// <param name="RightXOffset"></param> /// <param name="LeftXOffset"></param> /// <param name="RightYOffset"></param> /// <param name="LeftYOffset"></param> /// <param name="RightSectorNum">Num of the sector right to the wall (1=first, 0=unset)</param> /// <param name="LeftSectorNum">Num of the sector left to the wall (1=first, 0=unset)</param> public RooWall( uint RooVersion, short ServerID, ushort RightSideNum, ushort LeftSideNum, V2 P1, V2 P2, short RightXOffset, short LeftXOffset, short RightYOffset, short LeftYOffset, ushort RightSectorNum, ushort LeftSectorNum) { this.RooVersion = RooVersion; this.NextWallNumInPlane = ServerID; this.RightSideNum = RightSideNum; this.LeftSideNum = LeftSideNum; this.P1 = P1; this.P2 = P2; // set clientlength stored in 1:64 units (convert from 1:1024) this.ClientLength = (P1-P2).Length * 0.0625f; this.RightXOffset = RightXOffset; this.LeftXOffset = LeftXOffset; this.RightYOffset = RightYOffset; this.LeftYOffset = LeftYOffset; this.RightSectorNum = RightSectorNum; this.LeftSectorNum = LeftSectorNum; this.BowtieFlags = new BowtieFlags(); }
/// <summary> /// Returns a projection of this instance on another vector. /// </summary> /// <param name="Vector"></param> /// <returns></returns> public V2 GetProjection(V2 Vector) { Real denom = Vector.LengthSquared; if (denom > 0.0f) { Real num = this * Vector; return Vector * (num / denom); } else return V2.ZERO; }
/// <summary> /// Splits this wall into two using infinite line given by Q1Q2. /// </summary> /// <param name="Q1"></param> /// <param name="Q2"></param> /// <returns>Item1: P1 to I. Item2: I to P2</returns> public Tuple<RooWall, RooWall> Split(V2 Q1, V2 Q2) { V2 intersect; // intersect this wall (finite line) with the infinite line given by Q1Q2 LineInfiniteLineIntersectionType intersecttype = MathUtil.IntersectLineInfiniteLine(P1, P2, Q1, Q2, out intersect); // must have a real intersection, not only boundarypoint or even coincide if (intersecttype != LineInfiniteLineIntersectionType.OneIntersection) return null; /*****************************************************************/ // 1) Piece from P1 to intersection RooWall wall1 = new RooWall( RooVersion, NextWallNumInPlane, RightSideNum, LeftSideNum, P1, intersect, RightXOffset, // readjust below LeftXOffset, // readjust below RightYOffset, // readjust below LeftYOffset, // readjust below RightSectorNum, LeftSectorNum ); // also keep references of old wall wall1.RightSector = RightSector; wall1.LeftSector = LeftSector; wall1.RightSide = RightSide; wall1.LeftSide = LeftSide; wall1.BowtieFlags = BowtieFlags; wall1.CalculateWallSideHeights(); /*****************************************************************/ // 2) Piece from intersection to P2 RooWall wall2 = new RooWall( RooVersion, NextWallNumInPlane, RightSideNum, LeftSideNum, intersect, P2, RightXOffset, // readjust below LeftXOffset, // readjust below RightYOffset, // readjust below LeftYOffset, // readjust below RightSectorNum, LeftSectorNum ); // also keep references of old wall wall2.RightSector = RightSector; wall2.LeftSector = LeftSector; wall2.RightSide = RightSide; wall2.LeftSide = LeftSide; wall2.BowtieFlags = BowtieFlags; wall2.CalculateWallSideHeights(); /*****************************************************************/ // 3) Readjust texture offsets to accoutn for split // RightSide if (wall1.RightSide != null && wall1.RightSide.Flags.IsBackwards) wall1.RightXOffset += (short)wall2.ClientLength; else wall2.RightXOffset += (short)wall1.ClientLength; // LeftSide (Do this backwards, because client exchanges vertices of negative walls) if (wall1.LeftSide != null && wall1.LeftSide.Flags.IsBackwards) wall2.LeftXOffset += (short)wall1.ClientLength; else wall1.LeftXOffset += (short)wall2.ClientLength; /*****************************************************************/ return new Tuple<RooWall, RooWall>(wall1, wall2); }
/// <summary> /// Returns the scalar crossproduct of this vector /// and the parameter vector. /// </summary> /// <remarks> /// http://stackoverflow.com/questions/243945/calculating-a-2d-vectors-cross-product /// </remarks> /// <param name="v"></param> /// <returns></returns> public Real CrossProduct(V2 v) { return (X * v.Y) - (Y * v.X); }
/// <summary> /// Checks if this wall blocks /// a view ray from Start to End /// </summary> /// <param name="Start"></param> /// <param name="End"></param> /// <returns>True if blocked, false if OK</returns> public bool IsBlockingSight(V3 Start, V3 End) { // 2D V2 Start2D = new V2(Start.X, Start.Z); V2 End2D = new V2(End.X, End.Z); // calculate the sides of the points (returns -1, 0 or 1) int startside = Start2D.GetSide(P1, P2); int endside = End2D.GetSide(P1, P2); // if points are not on same side // the infinite lines cross if (startside != endside) { // verify also the finite line segments cross V2 intersect; LineLineIntersectionType intersecttype = MathUtil.IntersectLineLine(Start2D, End2D, P1, P2, out intersect); if (intersecttype == LineLineIntersectionType.OneIntersection || intersecttype == LineLineIntersectionType.OneBoundaryPoint || intersecttype == LineLineIntersectionType.FullyCoincide || intersecttype == LineLineIntersectionType.PartiallyCoincide) { // the vector/ray between start and end V3 diff = End - Start; // solve 3d linear equation to get rayheight R2 // (height of possible intersection) // // ( R1 ) (P1) (Q1) // ( R2 ) = (P2) + lambda * (Q2) // ( R3 ) (P3) (Q3) // // using: // R = intersect // P = Start // Q = diff (Direction) // get lambda scale Real lambda = 1.0f; if (diff.X != 0) lambda = (intersect.X - Start.X) / diff.X; else if (diff.Z != 0) lambda = (intersect.Y - Start.Z) / diff.Z; // calculate the rayheight based on linear 3d equation Real rayheight = Start.Y + lambda * diff.Y; // compare height with wallheights // use average of both endpoints (in case its sloped) // do not care about the sides Real h3 = (z3 + zz3) / 2.0f; Real h2 = (z2 + zz2) / 2.0f; Real h1 = (z1 + zz1) / 2.0f; Real h0 = (z0 + zz0) / 2.0f; bool a, b; // test upper part a = (startside <= 0 && LeftSide != null && LeftSide.ResourceUpper != null && (LeftSide.Flags.IsNoLookThrough || !LeftSide.Flags.IsTransparent)); b = (startside >= 0 && RightSide != null && RightSide.ResourceUpper != null && (RightSide.Flags.IsNoLookThrough || !RightSide.Flags.IsTransparent)); if ((a || b) && rayheight < h3 && rayheight > h2) return true; // test middle part a = (startside <= 0 && LeftSide != null && LeftSide.ResourceMiddle != null && (LeftSide.Flags.IsNoLookThrough || !LeftSide.Flags.IsTransparent)); b = (startside >= 0 && RightSide != null && RightSide.ResourceMiddle != null && (RightSide.Flags.IsNoLookThrough || !RightSide.Flags.IsTransparent)); if ((a || b) && rayheight < h2 && rayheight > h1) return true; // test lower part (nolookthrough) a = (startside <= 0 && LeftSide != null && LeftSide.ResourceLower != null); b = (startside >= 0 && RightSide != null && RightSide.ResourceLower != null); if ((a || b) && rayheight < h1 && rayheight > h0) return true; } } return false; }
/// <summary> /// Checks whether a point is inside the boundingbox /// </summary> /// <param name="P">Point to check</param> /// <param name="Extent"></param> /// <returns></returns> public bool IsInside(V2 P, Real Extent = 0.0f) { return (P.X + Extent >= Min.X && P.X - Extent <= Max.X && P.Y + Extent >= Min.Y && P.Y - Extent <= Max.Y); }