/// <summary> /// Determines the rectangular tile area that is visible to the specified <see cref="IDrawDevice"/>. /// </summary> /// <param name="device"></param> /// <param name="input"></param> public static TileOutput GetVisibleTileRect(IDrawDevice device, TileInput input) { TileOutput output; // Determine the view space transform of the tilemap float cameraScaleAtObj = device.GetScaleAtZ(input.TilemapPos.Z); Vector3 viewCenterWorldPos = device.GetWorldPos(new Vector3(device.TargetSize * 0.5f, input.TilemapPos.Z)); // Early-out, if so small that it might break the math behind rendering a single tile. if (cameraScaleAtObj <= 0.000000001f) { return(EmptyOutput); } // Determine transformed X and Y axis in world space output.XAxisWorld = Vector2.UnitX; output.YAxisWorld = Vector2.UnitY; MathF.TransformCoord(ref output.XAxisWorld.X, ref output.XAxisWorld.Y, input.TilemapAngle, input.TilemapScale); MathF.TransformCoord(ref output.YAxisWorld.X, ref output.YAxisWorld.Y, input.TilemapAngle, input.TilemapScale); // Determine which tile is in the center of view space. Point2 viewCenterTile = Point2.Zero; { Vector2 localViewCenter = (viewCenterWorldPos - input.TilemapPos).Xy; localViewCenter = new Vector2( Vector2.Dot(localViewCenter, output.XAxisWorld.Normalized), Vector2.Dot(localViewCenter, output.YAxisWorld.Normalized)) / input.TilemapScale; viewCenterTile = new Point2( (int)MathF.Floor(localViewCenter.X / input.TileSize.X), (int)MathF.Floor(localViewCenter.Y / input.TileSize.Y)); } // Determine the edge length of a square that is big enough to enclose the world space rect of the Camera view float visualAngle = input.TilemapAngle - device.ViewerAngle; Vector2 visualBounds = new Vector2( device.TargetSize.Y * MathF.Abs(MathF.Sin(visualAngle)) + device.TargetSize.X * MathF.Abs(MathF.Cos(visualAngle)), device.TargetSize.X * MathF.Abs(MathF.Sin(visualAngle)) + device.TargetSize.Y * MathF.Abs(MathF.Cos(visualAngle))); Vector2 localVisualBounds = visualBounds / cameraScaleAtObj; Point2 targetVisibleTileCount = new Point2( 3 + (int)MathF.Ceiling(localVisualBounds.X / (MathF.Min(input.TileSize.X, input.TileSize.Y) * input.TilemapScale)), 3 + (int)MathF.Ceiling(localVisualBounds.Y / (MathF.Min(input.TileSize.X, input.TileSize.Y) * input.TilemapScale))); // Determine the tile indices (xy) that are visible within that rect output.VisibleTileStart = new Point2( MathF.Max(viewCenterTile.X - targetVisibleTileCount.X / 2, 0), MathF.Max(viewCenterTile.Y - targetVisibleTileCount.Y / 2, 0)); Point2 tileGridEndPos = new Point2( MathF.Min(viewCenterTile.X + targetVisibleTileCount.X / 2, input.TileCount.X), MathF.Min(viewCenterTile.Y + targetVisibleTileCount.Y / 2, input.TileCount.Y)); output.VisibleTileCount = new Point2( MathF.Clamp(tileGridEndPos.X - output.VisibleTileStart.X, 0, input.TileCount.X), MathF.Clamp(tileGridEndPos.Y - output.VisibleTileStart.Y, 0, input.TileCount.Y)); // Determine start position for rendering output.RenderOriginWorld = input.TilemapPos + new Vector3( output.VisibleTileStart.X * output.XAxisWorld * input.TileSize.X + output.VisibleTileStart.Y * output.YAxisWorld * input.TileSize.Y); return(output); }
/// <summary> /// Transforms screen space to world space. /// </summary> /// <param name="screenPos"></param> /// <param name="device"></param> public static Vector3 GetWorldPos(this IDrawDevice device, Vector2 screenPos) { return(device.GetWorldPos(new Vector3(screenPos))); }