/// <summary> /// Moves a portable object along a ray, taking into account portal teleportation. Portal velocity is ignored for this method. /// </summary> /// <param name="portalable">Instance travelling along ray.</param> /// <param name="portals">Collection of portals used for portal teleportation.</param> /// <param name="portalEnter">Callback that is executed after entering a portal.</param> public static void RayCast(IPortalable portalable, IEnumerable<IPortal> portals, Settings settings, Action<EnterCallbackData, double> portalEnter = null) { Debug.Assert(settings.MaxIterations >= 0); Debug.Assert(portalable != null); Debug.Assert(portals != null); Debug.Assert(settings.TimeScale >= 0); Debug.Assert(!(portalable is IPortal)); if (portalable.GetVelocity().Position.Length == 0 || settings.TimeScale == 0) { return; } _rayCast( portalable, portals, portalable.GetVelocity().Position.Length * settings.TimeScale, null, portalEnter, settings, 0); }
private static void _rayCast(IPortalable placeable, IEnumerable<IPortal> portals, double movementLeft, IPortal portalPrevious, Action<EnterCallbackData, double> portalEnter, Settings settings, int count) { Transform2 begin = placeable.GetTransform(); Transform2 velocity = placeable.GetVelocity().Multiply(settings.TimeScale); if (settings.MaxIterations <= count) { //If we run out of iterations before running out of movement, call _rayEnd with 0 movementLeft just to make sure the AdjustEnpoint setting is handled. _rayEnd(placeable, portals, 0, portalPrevious, settings, begin, velocity); return; } if (!placeable.IsPortalable) { _rayEnd(placeable, portals, movementLeft, portalPrevious, settings, begin, velocity); return; } double distanceMin = movementLeft; IPortal portalNearest = null; IntersectCoord intersectNearest = new IntersectCoord(); LineF ray = new LineF(begin.Position, begin.Position + velocity.Position); foreach (IPortal p in portals) { if (!Portal.IsValid(p) || portalPrevious == p) { continue; } LineF portalLine = new LineF(Portal.GetWorldVerts(p)); IntersectCoord intersect = MathExt.LineLineIntersect(portalLine, ray, true); double distance = ((Vector2d)begin.Position - intersect.Position).Length; if (intersect.Exists && distance < distanceMin) { distanceMin = distance; portalNearest = p; intersectNearest = intersect; } } if (portalNearest != null) { movementLeft -= distanceMin; double t = (velocity.Position.Length - movementLeft) / velocity.Position.Length; begin.Position = (Vector2)intersectNearest.Position; placeable.SetTransform(begin); Portal.Enter(portalNearest, placeable, (float)intersectNearest.TFirst, true); portalEnter?.Invoke(new EnterCallbackData(portalNearest, placeable, intersectNearest.TFirst), t); movementLeft *= Math.Abs(placeable.GetTransform().Size / begin.Size); _rayCast(placeable, portals, movementLeft, portalNearest.Linked, portalEnter, settings, count + 1); } else { _rayEnd(placeable, portals, movementLeft, portalPrevious, settings, begin, velocity); } }
private static void _rayEnd(IPortalable placeable, IEnumerable<IPortal> portals, double movementLeft, IPortal portalPrevious, Settings settings, Transform2 begin, Transform2 velocity) { begin.Position += velocity.Position.Normalized() * (float)movementLeft; if (settings.AdjustEndpoint) { /*After the end position of the ray has been determined, adjust it's position so that it isn't too close to * any portal. Otherwise there is a risk of ambiguity as to which side of a portal the end point is on.*/ begin = AddMargin(portals, portalPrevious, begin, velocity); } placeable.SetTransform(begin); }