/// <summary> /// Intersect a line-of-sight line segment with a list of line segments. /// </summary> private static CSightSegment _IntersectLOSRayWithLines(List <CVisibilityBlockingSegment> Segs, Vector2 Start, Vector2 End, float Radius) { CSightSegment result = new CSightSegment(); Vector2 la = Start; Vector2 lb = End; Vector2 ld = (lb - la).normalized; float minT = (lb - la).magnitude; if (minT > Radius) { minT = Radius; lb = la + ld * minT; } // TODO: Not sure if this epsilon is needed. minT += 0.01f; Vector2 hitPoint = Vector2.zero; int segID = -1; for (int i = 0; i < Segs.Count; ++i) { CVisibilityBlockingSegment seg = Segs[i]; float t = 0.0f; Vector2 hp; if (CIntersections.LineVsLine(la, lb, seg.mA, seg.mB, out t, out hp)) { if (t >= 0 && t <= minT) { hitPoint = hp; minT = t; segID = i; } } } result.mAngle = Mathf.Atan2(ld.x, ld.y); result.mStart = la; result.mEnd = lb; result.mSegID = segID; if (segID != -1) { float len = (hitPoint - la).magnitude; result.mEnd = la + ld * len; } return(result); }
public void EdgePaintHandleUpdate(CVectorModel Model, ref SGizmoData Gizmo) { if (Gizmo.mGizmoHover != EGizmoHandleID.NONE) { return; } float minT = float.MaxValue; Vector3 planeHitPoint = Vector3.zero; int hitPlaneID = -1; bool planeWasHit = false; for (int i = 0; i < Model.mPlanes.Count; ++i) { CModelPlane p = Model.mPlanes[i]; Vector3 hit; float t; if (p.IntersectRay(Gizmo.mStartMouseRay, out hit, out t)) { planeWasHit = true; if (t < minT) { minT = t; planeHitPoint = hit; hitPlaneID = i; } } } if (planeWasHit) { CModelPlane p = Model.mPlanes[hitPlaneID]; // Find closest impact point on plane Vector3 projPoint; float d1 = CIntersections.PointVsLine(planeHitPoint, p.c1, p.c2, out projPoint); float d2 = CIntersections.PointVsLine(planeHitPoint, p.c2, p.c3, out projPoint); float d3 = CIntersections.PointVsLine(planeHitPoint, p.c3, p.c4, out projPoint); float d4 = CIntersections.PointVsLine(planeHitPoint, p.c4, p.c1, out projPoint); //CDebug.DrawLine(planeHitPoint, planeHitPoint + new Vector3(0, 0.1f, 0), Color.yellow, false); //CDebug.DrawLine(projPoint, projPoint + new Vector3(0, 0.1f, 0), Color.magenta, false); Gizmo.mGizmoHover = EGizmoHandleID.EDGE; Gizmo.mHoverID = hitPlaneID; if (d4 < d2 && d4 < d3 && d4 < d1) { Gizmo.mCornerID = 3; CDebug.DrawLine(p.c4, p.c1, Color.blue, false); } else if (d2 < d1 && d2 < d3 && d2 < d4) { Gizmo.mCornerID = 1; CDebug.DrawLine(p.c2, p.c3, Color.blue, false); } else if (d3 < d2 && d3 < d1 && d3 < d4) { Gizmo.mCornerID = 2; CDebug.DrawLine(p.c3, p.c4, Color.blue, false); } else { Gizmo.mCornerID = 0; CDebug.DrawLine(p.c1, p.c2, Color.blue, false); } } }
/// <summary> /// Get a list of points for building a line-of-site punch out. /// </summary> public static List <CSightSegment> GenerateLOSPoints(CUserWorldView WorldView, Vector2 StartPos, float Radius) { Vector2 start = StartPos; if (CGame.VarShowVisLines.mValue) { CDebug.DrawCircle(Vector3.up, StartPos.ToWorldVec3(), Radius, Color.red, false); CDebug.DrawYRectQuad(start.ToWorldVec3(), 0.25f, 0.25f, new Color(1.0f, 0.4f, 0.2f, 0.4f), false); } List <CVisibilityBlockingSegment> culledSegs = new List <CVisibilityBlockingSegment>(); List <CVisibilityBlockingSegment> visSegs = WorldView.GetVisSegments(); // Get wall segments that intersect view radius. // TODO: Speed up with acceleration structure. for (int i = 0; i < visSegs.Count; ++i) { CVisibilityBlockingSegment seg = visSegs[i]; Vector2 hitA; Vector2 hitB; bool OpenA, OpenB; if (CIntersections.LineVsCircle(seg.mA, seg.mB, StartPos, Radius, out hitA, out hitB, out OpenA, out OpenB)) { culledSegs.Add(new CVisibilityBlockingSegment(hitA, hitB)); if (CGame.VarShowVisLines.mValue) { CDebug.DrawLine(hitA.ToWorldVec3(), hitB.ToWorldVec3(), Color.black, false); } } } List <CSightSegment> lineSegs = new List <CSightSegment>(); if (culledSegs.Count == 0) { // TODO: Could precalculate this and store it, but this only helps improve best case. int count = 46; float interval = (Mathf.PI * 2.0f) / count; for (int i = 0; i < count; ++i) { CSightSegment seg = new CSightSegment(); lineSegs.Add(seg); seg.mEnd = new Vector2(Mathf.Sin(i * interval) * Radius, Mathf.Cos(i * interval) * Radius) + StartPos; } } else { // Find closest intersection points when casting a ray to each segment vertex. // TODO: There are still redundant casts to shared segment verts. for (int i = 0; i < culledSegs.Count; ++i) { CVisibilityBlockingSegment seg = culledSegs[i]; float offset = 0.001f; Vector2 shift; float angle = Mathf.Atan2((seg.mA - start).x, (seg.mA - start).y); shift.x = Mathf.Sin(angle - offset) * Radius + start.x; shift.y = Mathf.Cos(angle - offset) * Radius + start.y; lineSegs.Add(_IntersectLOSRayWithLines(culledSegs, start, shift, Radius)); shift.x = Mathf.Sin(angle + offset) * Radius + start.x; shift.y = Mathf.Cos(angle + offset) * Radius + start.y; lineSegs.Add(_IntersectLOSRayWithLines(culledSegs, start, shift, Radius)); angle = Mathf.Atan2((seg.mB - start).x, (seg.mB - start).y); shift.x = Mathf.Sin(angle - offset) * Radius + start.x; shift.y = Mathf.Cos(angle - offset) * Radius + start.y; lineSegs.Add(_IntersectLOSRayWithLines(culledSegs, start, shift, Radius)); shift.x = Mathf.Sin(angle + offset) * Radius + start.x; shift.y = Mathf.Cos(angle + offset) * Radius + start.y; lineSegs.Add(_IntersectLOSRayWithLines(culledSegs, start, shift, Radius)); if (CGame.VarShowVisLines.mValue) { //CDebug.DrawYRectQuad(seg.mA.ToWorldVec3(), 0.25f, 0.25f, new Color(1.0f, 0.4f, 0.2f, 0.4f), false); //CDebug.DrawYRectQuad(seg.mB.ToWorldVec3(), 0.25f, 0.25f, new Color(0.2f, 0.4f, 1.0f, 0.4f), false); CDebug.DrawLine(seg.mA.ToWorldVec3(), seg.mB.ToWorldVec3(), Color.red, false); } } // Sort intersections by angle. lineSegs.Sort((x, y) => { if (x.mAngle > y.mAngle) { return(1); } if (x.mAngle < y.mAngle) { return(-1); } return(0); }); // Cull redundant intersections. for (int i = 0; i < lineSegs.Count; ++i) { int s1 = lineSegs[i].mSegID; if (s1 == -1) { continue; } int s2 = lineSegs[(i + 1) % lineSegs.Count].mSegID; int s3 = lineSegs[(i + 2) % lineSegs.Count].mSegID; if (s1 == s2 && s1 == s3) { lineSegs.RemoveAt((i + 1) % lineSegs.Count); --i; } } // Regenerate circular caps. for (int i = 0; i < lineSegs.Count; ++i) { CSightSegment s1 = lineSegs[i]; CSightSegment s2 = lineSegs[(i + 1) % lineSegs.Count]; if (s1.mSegID == -1 && s2.mSegID == -1) { float angleD = s2.mAngle - s1.mAngle; if (i == lineSegs.Count - 1) { angleD = s2.mAngle + Mathf.PI * 2 - s1.mAngle; } int count = (int)(angleD / 0.1f); float interval = angleD / count; for (int j = 1; j < count; ++j) { float newAngle = s1.mAngle + interval * j; Vector2 shift; shift.x = Mathf.Sin(newAngle) * Radius + s1.mStart.x; shift.y = Mathf.Cos(newAngle) * Radius + s1.mStart.y; CSightSegment s = new CSightSegment(); s.mStart = s1.mStart; s.mEnd = shift; lineSegs.Insert(i + 1, s); ++i; } if (CGame.VarShowVisLines.mValue) { CDebug.DrawLine(s1.mEnd.ToWorldVec3(), s2.mEnd.ToWorldVec3(), Color.white, false); } } } if (CGame.VarShowVisLines.mValue) { for (int i = 0; i < lineSegs.Count; ++i) { CSightSegment s = lineSegs[i]; if (s.mSegID != -1) { CDebug.DrawLine(s.mStart.ToWorldVec3(), s.mEnd.ToWorldVec3(), Color.green, false); } else { CDebug.DrawYRectQuad(s.mEnd.ToWorldVec3(), 0.25f, 0.25f, Color.magenta, false); CDebug.DrawLine(s.mStart.ToWorldVec3(), s.mEnd.ToWorldVec3(), Color.blue, false); } } } } return(lineSegs); }