Exemple #1
0
        public void Render(Graphics g, Rectangle clip, float baseScale, PointF pos)
        {
            var startTime = DateTime.Now.Ticks;

            g.FillRectangle(_palette.Background, new Rectangle(0, 0, clip.X + clip.Width, clip.Y + clip.Height));
            g.InterpolationMode = InterpolationMode.NearestNeighbor;
            g.PixelOffsetMode   = PixelOffsetMode.None;

            var defaultFont = new Font("Arial", 10.0f, FontStyle.Bold);

            if (_mapper == null)
            {
                g.DrawString("Map object not initialized", defaultFont, _palette.Error, 5, 5);
                return;
            }

            var centerX = pos.X;
            var centerY = pos.Y;

            float totalX, totalY;

            if (clip.Width > clip.Height)
            {
                totalX = baseScale;
                totalY = baseScale * clip.Height / clip.Width;
            }
            else
            {
                totalY = baseScale;
                totalX = baseScale * clip.Width / clip.Height;
            }

            var startX = clip.X + centerX - totalX;
            var endX   = clip.X + centerX + totalX;
            var startY = clip.Y + centerY - totalY;
            var endY   = clip.Y + centerY + totalY;

            var scaleX = clip.Width / (endX - startX);
            var scaleY = clip.Height / (endY - startY);

            if (float.IsInfinity(scaleX) || float.IsNaN(scaleX))
            {
                scaleX = clip.Width;
            }
            if (float.IsInfinity(scaleY) || float.IsNaN(scaleY))
            {
                scaleY = clip.Height;
            }


            var ferryConnections = _mapper.FerryConnections.Where(item => !item.Hidden)
                                   .ToList();

            foreach (var ferryConnection in ferryConnections)
            {
                var connections = _mapper.LookupFerryConnection(ferryConnection.FerryPortId);

                foreach (var conn in connections)
                {
                    var newPoints = new List <PointF>
                    {
                        new PointF((conn.StartPortLocation.X - startX) * scaleX,
                                   (conn.StartPortLocation.Y - startY) * scaleY)
                    };

                    foreach (var connection in conn.connections)
                    {
                        newPoints.Add(new PointF((connection.X - startX) * scaleX, (connection.Y - startY) * scaleY));
                    }
                    newPoints.Add(new PointF((conn.EndPortLocation.X - startX) * scaleX, (conn.EndPortLocation.Y - startY) * scaleY));

                    var color = _palette.FerryLines;
                    if (_mapper.RouteFerryPorts.ContainsKey(_mapper.FerryPortbyId[conn.StartPortToken]) ||
                        _mapper.RouteFerryPorts.ContainsKey(_mapper.FerryPortbyId[conn.EndPortToken]))
                    {
                        color = _palette.NavColor;
                    }

                    var pen = new Pen(color, 50 * scaleX)
                    {
                        DashPattern = new[] { 10f, 10f }
                    };
                    g.DrawCurve(pen, newPoints.ToArray());
                }
            }

            var prefabs = _mapper.Prefabs.Where(item =>
                                                item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 &&
                                                item.Z <= endY + 1500 && !item.Hidden)
                          .ToList();

            List <TsPrefabLook> drawingQueue = new List <TsPrefabLook>();

            foreach (var prefabItem in prefabs) // TODO: Road Width
            {
                var originNode     = _mapper.GetNodeByUid(prefabItem.Nodes[0]);
                var mapPointOrigin = prefabItem.Prefab.PrefabNodes[prefabItem.Origin];

                var rot = (float)(originNode.Rotation - Math.PI - Math.Atan2(mapPointOrigin.RotZ, mapPointOrigin.RotX) + Math.PI / 2);

                var prefabStartX = originNode.X - mapPointOrigin.X;
                var prefabStartZ = originNode.Z - mapPointOrigin.Z;

                List <int> pointsDrawn = new List <int>();

                for (var i = 0; i < prefabItem.Prefab.MapPoints.Count; i++)
                {
                    var mapPoint = prefabItem.Prefab.MapPoints[i];
                    pointsDrawn.Add(i);

                    if (mapPoint.LaneCount == -1) // non-road Prefab
                    {
                        Dictionary <int, PointF> polyPoints = new Dictionary <int, PointF>();
                        var nextPoint = i;
                        do
                        {
                            foreach (var neighbour in prefabItem.Prefab.MapPoints[nextPoint].Neighbours)
                            {
                                if (!polyPoints.ContainsKey(neighbour)) // New Polygon Neighbour
                                {
                                    nextPoint = neighbour;
                                    var newPoint = RotatePoint(prefabStartX + prefabItem.Prefab.MapPoints[nextPoint].X,
                                                               prefabStartZ + prefabItem.Prefab.MapPoints[nextPoint].Z, rot, originNode.X, originNode.Z);

                                    polyPoints.Add(nextPoint,
                                                   new PointF((newPoint.X - startX) * scaleX,
                                                              (newPoint.Y - startY) * scaleY));
                                    break;
                                }
                                nextPoint = -1;
                            }
                        } while (nextPoint != -1);

                        var colorFlag = prefabItem.Prefab.MapPoints[polyPoints.First().Key].PrefabColorFlags;

                        Brush fillColor = _palette.PrefabLight;
                        if ((colorFlag & 0x02) != 0)
                        {
                            fillColor = _palette.PrefabLight;
                        }
                        else if ((colorFlag & 0x04) != 0)
                        {
                            fillColor = _palette.PrefabDark;
                        }
                        else if ((colorFlag & 0x08) != 0)
                        {
                            fillColor = _palette.PrefabGreen;
                        }
                        // else fillColor = _palette.Error; // Unknown

                        var prefabLook = new TsPrefabPolyLook(polyPoints.Values.ToList())
                        {
                            ZIndex = ((colorFlag & 0x01) != 0) ? 3 : 2,
                            Color  = fillColor
                        };

                        drawingQueue.Add(prefabLook);
                        continue;
                    }
                    // This part is now made by prefab curves

                    /*
                     * foreach (var neighbourPointIndex in mapPoint.Neighbours) // TODO: Fix connection between road segments
                     * {
                     * if (pointsDrawn.Contains(neighbourPointIndex)) continue;
                     * var neighbourPoint = prefabItem.Prefab.MapPoints[neighbourPointIndex];
                     *
                     * if ((mapPoint.Hidden || neighbourPoint.Hidden) && prefabItem.Prefab.PrefabNodes.Count + 1 <
                     *  prefabItem.Prefab.MapPoints.Count) continue;
                     *
                     * var newPointStart = RotatePoint(prefabStartX + mapPoint.X,
                     *  prefabStartZ + mapPoint.Z, rot, originNode.X, originNode.Z);
                     *
                     * var newPointEnd = RotatePoint(prefabStartX + neighbourPoint.X,
                     *  prefabStartZ + neighbourPoint.Z, rot, originNode.X, originNode.Z);
                     *
                     * TsPrefabLook prefabLook = new TsPrefabRoadLook()
                     * {
                     *  Color = _palette.PrefabRoad,
                     *  ZIndex = 4,
                     *  Width = 10f * scaleX,
                     * };
                     *
                     * prefabLook.AddPoint((newPointStart.X - startX) * scaleX, (newPointStart.Y - startY) * scaleY);
                     * prefabLook.AddPoint((newPointEnd.X - startX) * scaleX, (newPointEnd.Y - startY) * scaleY);
                     *
                     * drawingQueue.Add(prefabLook);
                     * }
                     */
                }

                for (int i = 0; i < prefabItem.Prefab.PrefabCurves.Count; i++)
                {
                    var newPointStart = RotatePoint(prefabStartX + prefabItem.Prefab.PrefabCurves[i].start_X, prefabStartZ + prefabItem.Prefab.PrefabCurves[i].start_Z, rot, originNode.X, originNode.Z);
                    var newPointEnd   = RotatePoint(prefabStartX + prefabItem.Prefab.PrefabCurves[i].end_X, prefabStartZ + prefabItem.Prefab.PrefabCurves[i].end_Z, rot, originNode.X, originNode.Z);
                    var color         = _palette.PrefabRoad;
                    var zind          = 4;

                    if ((_mapper.PrefabNav.ContainsKey(prefabItem) && _mapper.PrefabNav.ContainsKey(prefabItem) && _mapper.PrefabNav[prefabItem].Contains(prefabItem.Prefab.PrefabCurves[i])) || _mapper.RoutePrefabs.Contains(prefabItem))
                    {
                        color = _palette.NavColor;
                        zind  = 1000;
                    }

                    TsPrefabLook prefabLook = new TsPrefabRoadLook()
                    {
                        Color  = color,
                        Width  = 10f * scaleX,
                        ZIndex = zind
                    };

                    prefabLook.AddPoint((newPointStart.X - startX) * scaleX, (newPointStart.Y - startY) * scaleY);
                    prefabLook.AddPoint((newPointEnd.X - startX) * scaleX, (newPointEnd.Y - startY) * scaleY);

                    drawingQueue.Add(prefabLook);
                }
            }

            foreach (var prefabLook in drawingQueue.OrderBy(p => p.ZIndex))
            {
                prefabLook.Draw(g);
            }

            var roads = _mapper.Roads.Where(item =>
                                            item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 &&
                                            item.Z <= endY + 1500 && !item.Hidden)
                        .ToList();

            foreach (var road in roads)
            {
                var startNode = road.GetStartNode();
                var endNode   = road.GetEndNode();

                if (road.GetPoints() == null)
                {
                    var newPoints = new List <PointF>();

                    var sx = startNode.X;
                    var sz = startNode.Z;
                    var ex = endNode.X;
                    var ez = endNode.Z;

                    var radius = Math.Sqrt(Math.Pow(sx - ex, 2) + Math.Pow(sz - ez, 2));

                    var tanSx = Math.Cos(-(Math.PI * 0.5f - startNode.Rotation)) * radius;
                    var tanEx = Math.Cos(-(Math.PI * 0.5f - endNode.Rotation)) * radius;
                    var tanSz = Math.Sin(-(Math.PI * 0.5f - startNode.Rotation)) * radius;
                    var tanEz = Math.Sin(-(Math.PI * 0.5f - endNode.Rotation)) * radius;

                    for (var i = 0; i < 8; i++)
                    {
                        var s = i / (float)(8 - 1);
                        var x = (float)TsRoadLook.Hermite(s, sx, ex, tanSx, tanEx);
                        var z = (float)TsRoadLook.Hermite(s, sz, ez, tanSz, tanEz);
                        newPoints.Add(new PointF(x, z));
                    }
                    road.AddPoints(newPoints);
                }

                var points = road.GetPoints();

                for (var i = 0; i < points.Length; i++)
                {
                    var point = points[i];
                    points[i] = new PointF((point.X - startX) * scaleX, (point.Y - startY) * scaleY);
                }

                var roadWidth = road.RoadLook.GetWidth() * scaleX;

                var color = _palette.Road;
                if (_mapper.RouteRoads.Contains(road))
                {
                    color = _palette.NavColor;
                }

                g.DrawCurve(new Pen(color, roadWidth), points.ToArray());
            }


            var cities = _mapper.Cities.Where(item =>
                                              item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 &&
                                              item.Z <= endY + 1500 && !item.Hidden)
                         .ToList();

            foreach (var city in cities)
            {
                var cityFont = new Font("Arial", 80 * scaleX, FontStyle.Bold);
                g.DrawString(city.CityName, cityFont, _palette.CityName, (city.X - startX) * scaleX, (city.Z - startY) * scaleY);
            }

            var overlays = _mapper.MapOverlays.Where(item =>
                                                     item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 &&
                                                     item.Z <= endY + 1500)
                           .ToList();

            foreach (var overlayItem in overlays) // TODO: Scaling
            {
                Bitmap b = overlayItem.Overlay.GetBitmap();
                if (b != null)
                {
                    g.DrawImage(b, (overlayItem.X - b.Width - startX) * scaleX, (overlayItem.Z - b.Height - startY) * scaleY,
                                b.Width * 2 * scaleX, b.Height * 2 * scaleY);
                }
            }

            var companies = _mapper.Companies.Where(item =>
                                                    item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 &&
                                                    item.Z <= endY + 1500)
                            .ToList();

            foreach (var companyItem in companies) // TODO: Scaling
            {
                Bitmap b = companyItem.Overlay.GetBitmap();
                if (b != null)
                {
                    g.DrawImage(b, (companyItem.X - startX) * scaleX, (companyItem.Z - startY) * scaleY, b.Width * scaleX, b.Height * scaleY);
                }
            }

            foreach (var prefab in prefabs) // Draw all prefab overlays
            {
                var originNode     = _mapper.GetNodeByUid(prefab.Nodes[0]);
                var mapPointOrigin = prefab.Prefab.PrefabNodes[prefab.Origin];

                var rot = (float)(originNode.Rotation - Math.PI - Math.Atan2(mapPointOrigin.RotZ, mapPointOrigin.RotX) + Math.PI / 2);

                var prefabStartX = originNode.X - mapPointOrigin.X;
                var prefabStartZ = originNode.Z - mapPointOrigin.Z;
                foreach (var spawnPoint in prefab.Prefab.SpawnPoints)
                {
                    var newPoint = RotatePoint(prefabStartX + spawnPoint.X, prefabStartZ + spawnPoint.Z, rot,
                                               originNode.X, originNode.Z);

                    switch (spawnPoint.Type)
                    {
                    case TsSpawnPointType.Fuel:
                    {
                        var    overlay = _mapper.LookupOverlay(0x11C686A54F);
                        Bitmap b       = overlay?.GetBitmap();

                        if (b != null)
                        {
                            g.DrawImage(b, (newPoint.X - b.Width / 2f - startX) * scaleX, (newPoint.Y - b.Height / 2f - startY) * scaleY,
                                        b.Width * scaleX, b.Height * scaleY);
                        }
                        break;
                    }

                    case TsSpawnPointType.Service:
                    {
                        var    overlay = _mapper.LookupOverlay(0x2358E7493388A97);
                        Bitmap b       = overlay?.GetBitmap();

                        if (b != null)
                        {
                            g.DrawImage(b, (newPoint.X - b.Width / 2f - startX) * scaleX, (newPoint.Y - b.Height / 2f - startY) * scaleY,
                                        b.Width * scaleX, b.Height * scaleY);
                        }
                        break;
                    }

                    case TsSpawnPointType.WeightStation:
                    {
                        var    overlay = _mapper.LookupOverlay(0xD50E1058FBBF179F);
                        Bitmap b       = overlay?.GetBitmap();

                        if (b != null)
                        {
                            g.DrawImage(b, (newPoint.X - b.Width / 2f - startX) * scaleX, (newPoint.Y - b.Height / 2f - startY) * scaleY,
                                        b.Width * scaleX, b.Height * scaleY);
                        }
                        break;
                    }

                    case TsSpawnPointType.TruckDealer:
                    {
                        var    overlay = _mapper.LookupOverlay(0xEE210C8438914);
                        Bitmap b       = overlay?.GetBitmap();

                        if (b != null)
                        {
                            g.DrawImage(b, (newPoint.X - b.Width / 2f - startX) * scaleX, (newPoint.Y - b.Height / 2f - startY) * scaleY,
                                        b.Width * scaleX, b.Height * scaleY);
                        }
                        break;
                    }

                    case TsSpawnPointType.GarageOutdoor:
                    {
                        var    overlay = _mapper.LookupOverlay(0x4572831B4D58CC5B);
                        Bitmap b       = overlay?.GetBitmap();

                        if (b != null)
                        {
                            g.DrawImage(b, (newPoint.X - b.Width / 2f - startX) * scaleX, (newPoint.Y - b.Height / 2f - startY) * scaleY,
                                        b.Width * scaleX, b.Height * scaleY);
                        }
                        break;
                    }

                    case TsSpawnPointType.Recruitment:
                    {
                        var    overlay = _mapper.LookupOverlay(0x1E18DD7A560F3E5A);
                        Bitmap b       = overlay?.GetBitmap();

                        if (b != null)
                        {
                            g.DrawImage(b, (newPoint.X - b.Width / 2f - startX) * scaleX, (newPoint.Y - b.Height / 2f - startY) * scaleY,
                                        b.Width * scaleX, b.Height * scaleY);
                        }
                        break;
                    }
                    }
                }

                var lastId = -1;
                foreach (var triggerPoint in prefab.Prefab.TriggerPoints) // trigger points in prefabs: garage, hotel, ...
                {
                    var newPoint = RotatePoint(prefabStartX + triggerPoint.X, prefabStartZ + triggerPoint.Z, rot,
                                               originNode.X, originNode.Z);

                    if (triggerPoint.TriggerId == lastId)
                    {
                        continue;
                    }
                    lastId = (int)triggerPoint.TriggerId;

                    if (triggerPoint.TriggerActionUid == 0x18991B7A99E279C) // parking trigger
                    {
                        var    overlay = _mapper.LookupOverlay(0x2358E762E112CD4);
                        Bitmap b       = overlay?.GetBitmap();

                        if (b != null)
                        {
                            g.DrawImage(b, (newPoint.X - b.Width / 2f - startX) * scaleX, (newPoint.Y - b.Height / 2f - startY) * scaleY,
                                        b.Width * scaleX, b.Height * scaleY);
                        }
                    }
                }
            }

            var ferryItems = _mapper.FerryConnections.Where(item =>
                                                            item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 &&
                                                            item.Z <= endY + 1500)
                             .ToList();

            foreach (var ferryItem in ferryItems) // TODO: Scaling
            {
                Bitmap b = ferryItem.Overlay?.GetBitmap();
                if (b != null)
                {
                    g.DrawImage(b, (ferryItem.X - startX) * scaleX, (ferryItem.Z - startY) * scaleY, b.Width * scaleX, b.Height * scaleY);
                }
            }

            var elapsedTime = DateTime.Now.Ticks - startTime;

            g.DrawString($"DrawTime: {elapsedTime / TimeSpan.TicksPerMillisecond} ms, x: {centerX}, y: {centerY}, scale: {baseScale}", defaultFont, Brushes.WhiteSmoke, 5, 5);
        }
Exemple #2
0
        public void Render(Graphics g, Rectangle clip, float scale, PointF startPoint, MapPalette palette, RenderFlags renderFlags = RenderFlags.All)
        {
            var startTime = DateTime.Now.Ticks;

            g.ResetTransform();
            g.FillRectangle(palette.Background, new Rectangle(0, 0, clip.Width, clip.Height));

            g.ScaleTransform(scale, scale);
            g.TranslateTransform(-startPoint.X, -startPoint.Y);
            g.InterpolationMode = InterpolationMode.NearestNeighbor;
            g.PixelOffsetMode   = PixelOffsetMode.None;
            g.SmoothingMode     = SmoothingMode.AntiAlias;

            if (_mapper == null)
            {
                g.DrawString("Map object not initialized", _defaultFont, palette.Error, 5, 5);
                return;
            }

            var zoomIndex = RenderHelper.GetZoomIndex(clip, scale);

            var endPoint = new PointF(startPoint.X + clip.Width / scale, startPoint.Y + clip.Height / scale);

            var ferryStartTime = DateTime.Now.Ticks;

            if (renderFlags.IsActive(RenderFlags.FerryConnections))
            {
                var ferryConnections = _mapper.FerryConnections.Where(item => !item.Hidden)
                                       .ToList();

                var ferryPen = new Pen(palette.FerryLines, 50)
                {
                    DashPattern = new[] { 10f, 10f }
                };

                foreach (var ferryConnection in ferryConnections)
                {
                    var connections = _mapper.LookupFerryConnection(ferryConnection.FerryPortId);

                    foreach (var conn in connections)
                    {
                        if (conn.Connections.Count == 0) // no extra nodes -> straight line
                        {
                            g.DrawLine(ferryPen, conn.StartPortLocation, conn.EndPortLocation);
                            continue;
                        }

                        var startYaw = Math.Atan2(conn.Connections[0].Z - conn.StartPortLocation.Y, // get angle of the start port to the first node
                                                  conn.Connections[0].X - conn.StartPortLocation.X);
                        var bezierNodes = RenderHelper.GetBezierControlNodes(conn.StartPortLocation.X,
                                                                             conn.StartPortLocation.Y, startYaw, conn.Connections[0].X, conn.Connections[0].Z,
                                                                             conn.Connections[0].Rotation);

                        var bezierPoints = new List <PointF>
                        {
                            new PointF(conn.StartPortLocation.X, conn.StartPortLocation.Y),                                             // start
                            new PointF(conn.StartPortLocation.X + bezierNodes.Item1.X, conn.StartPortLocation.Y + bezierNodes.Item1.Y), // control1
                            new PointF(conn.Connections[0].X - bezierNodes.Item2.X, conn.Connections[0].Z - bezierNodes.Item2.Y),       // control2
                            new PointF(conn.Connections[0].X, conn.Connections[0].Z)
                        };

                        for (var i = 0; i < conn.Connections.Count - 1; i++) // loop all extra nodes
                        {
                            var ferryPoint     = conn.Connections[i];
                            var nextFerryPoint = conn.Connections[i + 1];

                            bezierNodes = RenderHelper.GetBezierControlNodes(ferryPoint.X, ferryPoint.Z, ferryPoint.Rotation,
                                                                             nextFerryPoint.X, nextFerryPoint.Z, nextFerryPoint.Rotation);

                            bezierPoints.Add(new PointF(ferryPoint.X + bezierNodes.Item1.X, ferryPoint.Z + bezierNodes.Item1.Y));         // control1
                            bezierPoints.Add(new PointF(nextFerryPoint.X - bezierNodes.Item2.X, nextFerryPoint.Z - bezierNodes.Item2.Y)); // control2
                            bezierPoints.Add(new PointF(nextFerryPoint.X, nextFerryPoint.Z));                                             // end
                        }

                        var lastFerryPoint = conn.Connections[conn.Connections.Count - 1];
                        var endYaw         = Math.Atan2(conn.EndPortLocation.Y - lastFerryPoint.Z, // get angle of the last node to the end port
                                                        conn.EndPortLocation.X - lastFerryPoint.X);

                        bezierNodes = RenderHelper.GetBezierControlNodes(lastFerryPoint.X,
                                                                         lastFerryPoint.Z, lastFerryPoint.Rotation, conn.EndPortLocation.X, conn.EndPortLocation.Y,
                                                                         endYaw);

                        bezierPoints.Add(new PointF(lastFerryPoint.X + bezierNodes.Item1.X, lastFerryPoint.Z + bezierNodes.Item1.Y));             // control1
                        bezierPoints.Add(new PointF(conn.EndPortLocation.X - bezierNodes.Item2.X, conn.EndPortLocation.Y - bezierNodes.Item2.Y)); // control2
                        bezierPoints.Add(new PointF(conn.EndPortLocation.X, conn.EndPortLocation.Y));                                             // end

                        g.DrawBeziers(ferryPen, bezierPoints.ToArray());
                    }
                }
                ferryPen.Dispose();
            }
            var ferryTime = DateTime.Now.Ticks - ferryStartTime;

            var mapAreaStartTime = DateTime.Now.Ticks;

            if (renderFlags.IsActive(RenderFlags.MapAreas))
            {
                var mapAreas = _mapper.MapAreas.Where(item =>
                                                      item.X >= startPoint.X - itemDrawMargin && item.X <= endPoint.X + itemDrawMargin && item.Z >= startPoint.Y - itemDrawMargin &&
                                                      item.Z <= endPoint.Y + itemDrawMargin && !item.Hidden)
                               .ToList();


                foreach (var mapArea in mapAreas.OrderBy(x => x.DrawOver))
                {
                    var points = new List <PointF>();

                    foreach (var mapAreaNode in mapArea.NodeUids)
                    {
                        var node = _mapper.GetNodeByUid(mapAreaNode);
                        if (node == null)
                        {
                            continue;
                        }
                        points.Add(new PointF(node.X, node.Z));
                    }

                    Brush fillColor = palette.PrefabLight;
                    if ((mapArea.ColorIndex & 0x01) != 0)
                    {
                        fillColor = palette.PrefabLight;
                    }
                    else if ((mapArea.ColorIndex & 0x02) != 0)
                    {
                        fillColor = palette.PrefabDark;
                    }
                    else if ((mapArea.ColorIndex & 0x03) != 0)
                    {
                        fillColor = palette.PrefabGreen;
                    }

                    g.FillPolygon(fillColor, points.ToArray());
                }
            }
            var mapAreaTime = DateTime.Now.Ticks - mapAreaStartTime;

            var prefabStartTime = DateTime.Now.Ticks;
            var prefabs         = _mapper.Prefabs.Where(item =>
                                                        item.X >= startPoint.X - itemDrawMargin && item.X <= endPoint.X + itemDrawMargin && item.Z >= startPoint.Y - itemDrawMargin &&
                                                        item.Z <= endPoint.Y + itemDrawMargin && !item.Hidden)
                                  .ToList();

            if (renderFlags.IsActive(RenderFlags.Prefabs))
            {
                List <TsPrefabLook> drawingQueue = new List <TsPrefabLook>();

                foreach (var prefabItem in prefabs)
                {
                    var originNode = _mapper.GetNodeByUid(prefabItem.Nodes[0]);
                    if (prefabItem.Prefab.PrefabNodes == null)
                    {
                        continue;
                    }

                    if (!prefabItem.HasLooks())
                    {
                        var mapPointOrigin = prefabItem.Prefab.PrefabNodes[prefabItem.Origin];

                        var rot = (float)(originNode.Rotation - Math.PI -
                                          Math.Atan2(mapPointOrigin.RotZ, mapPointOrigin.RotX) + Math.PI / 2);

                        var prefabstartX = originNode.X - mapPointOrigin.X;
                        var prefabStartZ = originNode.Z - mapPointOrigin.Z;

                        List <int> pointsDrawn = new List <int>();

                        for (var i = 0; i < prefabItem.Prefab.MapPoints.Count; i++)
                        {
                            var mapPoint = prefabItem.Prefab.MapPoints[i];
                            pointsDrawn.Add(i);

                            if (mapPoint.LaneCount == -1) // non-road Prefab
                            {
                                Dictionary <int, PointF> polyPoints = new Dictionary <int, PointF>();
                                var nextPoint = i;
                                do
                                {
                                    if (prefabItem.Prefab.MapPoints[nextPoint].Neighbours.Count == 0)
                                    {
                                        break;
                                    }

                                    foreach (var neighbour in prefabItem.Prefab.MapPoints[nextPoint].Neighbours)
                                    {
                                        if (!polyPoints.ContainsKey(neighbour)) // New Polygon Neighbour
                                        {
                                            nextPoint = neighbour;
                                            var newPoint = RenderHelper.RotatePoint(
                                                prefabstartX + prefabItem.Prefab.MapPoints[nextPoint].X,
                                                prefabStartZ + prefabItem.Prefab.MapPoints[nextPoint].Z, rot, originNode.X,
                                                originNode.Z);

                                            polyPoints.Add(nextPoint, new PointF(newPoint.X, newPoint.Y));
                                            break;
                                        }
                                        nextPoint = -1;
                                    }
                                } while (nextPoint != -1);

                                if (polyPoints.Count < 2)
                                {
                                    continue;
                                }

                                var colorFlag = prefabItem.Prefab.MapPoints[polyPoints.First().Key].PrefabColorFlags;

                                Brush fillColor = palette.PrefabLight;
                                if ((colorFlag & 0x02) != 0)
                                {
                                    fillColor = palette.PrefabLight;
                                }
                                else if ((colorFlag & 0x04) != 0)
                                {
                                    fillColor = palette.PrefabDark;
                                }
                                else if ((colorFlag & 0x08) != 0)
                                {
                                    fillColor = palette.PrefabGreen;
                                }
                                // else fillColor = _palette.Error; // Unknown

                                var prefabLook = new TsPrefabPolyLook(polyPoints.Values.ToList())
                                {
                                    ZIndex = ((colorFlag & 0x01) != 0) ? 3 : 2,
                                    Color  = fillColor
                                };

                                prefabItem.AddLook(prefabLook);
                                continue;
                            }

                            var mapPointLaneCount = mapPoint.LaneCount;

                            if (mapPointLaneCount == -2 && i < prefabItem.Prefab.PrefabNodes.Count)
                            {
                                if (mapPoint.ControlNodeIndex != -1)
                                {
                                    mapPointLaneCount = prefabItem.Prefab.PrefabNodes[mapPoint.ControlNodeIndex].LaneCount;
                                }
                            }

                            foreach (var neighbourPointIndex in mapPoint.Neighbours) // TODO: Fix connection between road segments
                            {
                                if (pointsDrawn.Contains(neighbourPointIndex))
                                {
                                    continue;
                                }
                                var neighbourPoint = prefabItem.Prefab.MapPoints[neighbourPointIndex];

                                if ((mapPoint.Hidden || neighbourPoint.Hidden) && prefabItem.Prefab.PrefabNodes.Count + 1 <
                                    prefabItem.Prefab.MapPoints.Count)
                                {
                                    continue;
                                }

                                var roadYaw = Math.Atan2(neighbourPoint.Z - mapPoint.Z, neighbourPoint.X - mapPoint.X);

                                var neighbourLaneCount = neighbourPoint.LaneCount;

                                if (neighbourLaneCount == -2 && neighbourPointIndex < prefabItem.Prefab.PrefabNodes.Count)
                                {
                                    if (neighbourPoint.ControlNodeIndex != -1)
                                    {
                                        neighbourLaneCount = prefabItem.Prefab.PrefabNodes[neighbourPoint.ControlNodeIndex].LaneCount;
                                    }
                                }

                                if (mapPointLaneCount == -2 && neighbourLaneCount != -2)
                                {
                                    mapPointLaneCount = neighbourLaneCount;
                                }
                                else if (neighbourLaneCount == -2 && mapPointLaneCount != -2)
                                {
                                    neighbourLaneCount = mapPointLaneCount;
                                }
                                else if (mapPointLaneCount == -2 && neighbourLaneCount == -2)
                                {
                                    Console.WriteLine($"Could not find lane count for ({i}, {neighbourPointIndex}), defaulting to 1 for {prefabItem.Prefab.FilePath}");
                                    mapPointLaneCount = neighbourLaneCount = 1;
                                }

                                var cornerCoords = new List <PointF>();

                                var coords = RenderHelper.GetCornerCoords(prefabstartX + mapPoint.X, prefabStartZ + mapPoint.Z,
                                                                          (Common.LaneWidth * mapPointLaneCount + mapPoint.LaneOffset) / 2f, roadYaw + Math.PI / 2);

                                cornerCoords.Add(RenderHelper.RotatePoint(coords.X, coords.Y, rot, originNode.X, originNode.Z));

                                coords = RenderHelper.GetCornerCoords(prefabstartX + neighbourPoint.X, prefabStartZ + neighbourPoint.Z,
                                                                      (Common.LaneWidth * neighbourLaneCount + neighbourPoint.LaneOffset) / 2f,
                                                                      roadYaw + Math.PI / 2);
                                cornerCoords.Add(RenderHelper.RotatePoint(coords.X, coords.Y, rot, originNode.X, originNode.Z));

                                coords = RenderHelper.GetCornerCoords(prefabstartX + neighbourPoint.X, prefabStartZ + neighbourPoint.Z,
                                                                      (Common.LaneWidth * neighbourLaneCount + mapPoint.LaneOffset) / 2f,
                                                                      roadYaw - Math.PI / 2);
                                cornerCoords.Add(RenderHelper.RotatePoint(coords.X, coords.Y, rot, originNode.X, originNode.Z));

                                coords = RenderHelper.GetCornerCoords(prefabstartX + mapPoint.X, prefabStartZ + mapPoint.Z,
                                                                      (Common.LaneWidth * mapPointLaneCount + mapPoint.LaneOffset) / 2f, roadYaw - Math.PI / 2);
                                cornerCoords.Add(RenderHelper.RotatePoint(coords.X, coords.Y, rot, originNode.X, originNode.Z));

                                TsPrefabLook prefabLook = new TsPrefabPolyLook(cornerCoords)
                                {
                                    Color  = palette.PrefabRoad,
                                    ZIndex = 4,
                                };

                                prefabItem.AddLook(prefabLook);
                            }
                        }
                    }

                    prefabItem.GetLooks().ForEach(x => drawingQueue.Add(x));
                }

                foreach (var prefabLook in drawingQueue.OrderBy(p => p.ZIndex))
                {
                    prefabLook.Draw(g);
                }
            }
            var prefabTime = DateTime.Now.Ticks - prefabStartTime;

            var roadStartTime = DateTime.Now.Ticks;

            if (renderFlags.IsActive(RenderFlags.Roads))
            {
                var roads = _mapper.Roads.Where(item =>
                                                item.X >= startPoint.X - itemDrawMargin && item.X <= endPoint.X + itemDrawMargin && item.Z >= startPoint.Y - itemDrawMargin &&
                                                item.Z <= endPoint.Y + itemDrawMargin && !item.Hidden)
                            .ToList();

                foreach (var road in roads)
                {
                    var startNode = road.GetStartNode();
                    var endNode   = road.GetEndNode();

                    if (!road.HasPoints())
                    {
                        var newPoints = new List <PointF>();

                        var sx = startNode.X;
                        var sz = startNode.Z;
                        var ex = endNode.X;
                        var ez = endNode.Z;

                        var radius = Math.Sqrt(Math.Pow(sx - ex, 2) + Math.Pow(sz - ez, 2));

                        var tanSx = Math.Cos(-(Math.PI * 0.5f - startNode.Rotation)) * radius;
                        var tanEx = Math.Cos(-(Math.PI * 0.5f - endNode.Rotation)) * radius;
                        var tanSz = Math.Sin(-(Math.PI * 0.5f - startNode.Rotation)) * radius;
                        var tanEz = Math.Sin(-(Math.PI * 0.5f - endNode.Rotation)) * radius;

                        for (var i = 0; i < 8; i++)
                        {
                            var s = i / (float)(8 - 1);
                            var x = (float)TsRoadLook.Hermite(s, sx, ex, tanSx, tanEx);
                            var z = (float)TsRoadLook.Hermite(s, sz, ez, tanSz, tanEz);
                            newPoints.Add(new PointF(x, z));
                        }
                        road.AddPoints(newPoints);
                    }

                    var roadWidth = road.RoadLook.GetWidth();

                    var roadPen = new Pen(palette.Road, roadWidth);
                    g.DrawCurve(roadPen, road.GetPoints()?.ToArray());
                    roadPen.Dispose();
                }
            }
            var roadTime = DateTime.Now.Ticks - roadStartTime;

            var mapOverlayStartTime = DateTime.Now.Ticks;

            if (renderFlags.IsActive(RenderFlags.MapOverlays))
            {
                var overlays = _mapper.MapOverlays.Where(item =>
                                                         item.X >= startPoint.X - itemDrawMargin && item.X <= endPoint.X + itemDrawMargin && item.Z >= startPoint.Y - itemDrawMargin &&
                                                         item.Z <= endPoint.Y + itemDrawMargin && !item.Hidden)
                               .ToList();

                foreach (var overlayItem in overlays) // TODO: Scaling
                {
                    Bitmap b = overlayItem.Overlay.GetBitmap();
                    if (b != null)
                    {
                        g.DrawImage(b, overlayItem.X - b.Width, overlayItem.Z - b.Height, b.Width * 2, b.Height * 2);
                    }
                }
            }
            var mapOverlayTime = DateTime.Now.Ticks - mapOverlayStartTime;

            var mapOverlay2StartTime = DateTime.Now.Ticks;

            if (renderFlags.IsActive(RenderFlags.MapOverlays))
            {
                var companies = _mapper.Companies.Where(item =>
                                                        item.X >= startPoint.X - itemDrawMargin && item.X <= endPoint.X + itemDrawMargin && item.Z >= startPoint.Y - itemDrawMargin &&
                                                        item.Z <= endPoint.Y + itemDrawMargin && !item.Hidden)
                                .ToList();

                foreach (var companyItem in companies) // TODO: Scaling
                {
                    var point = new PointF(companyItem.X, companyItem.Z);
                    if (companyItem.Nodes.Count > 0)
                    {
                        var prefab = _mapper.Prefabs.FirstOrDefault(x => x.Uid == companyItem.Nodes[0]);
                        if (prefab != null)
                        {
                            var originNode = _mapper.GetNodeByUid(prefab.Nodes[0]);
                            if (prefab.Prefab.PrefabNodes == null)
                            {
                                continue;
                            }
                            var mapPointOrigin = prefab.Prefab.PrefabNodes[prefab.Origin];

                            var rot = (float)(originNode.Rotation - Math.PI -
                                              Math.Atan2(mapPointOrigin.RotZ, mapPointOrigin.RotX) + Math.PI / 2);

                            var prefabstartX = originNode.X - mapPointOrigin.X;
                            var prefabStartZ = originNode.Z - mapPointOrigin.Z;
                            var companyPos   = prefab.Prefab.SpawnPoints.FirstOrDefault(x => x.Type == TsSpawnPointType.CompanyPos);
                            if (companyPos != null)
                            {
                                point = RenderHelper.RotatePoint(prefabstartX + companyPos.X,
                                                                 prefabStartZ + companyPos.Z, rot,
                                                                 originNode.X, originNode.Z);
                            }
                        }
                    }
                    Bitmap b = companyItem.Overlay?.GetBitmap();
                    if (b != null)
                    {
                        g.DrawImage(b, point.X, point.Y, b.Width, b.Height);
                    }
                }

                foreach (var prefab in prefabs) // Draw all prefab overlays
                {
                    var originNode = _mapper.GetNodeByUid(prefab.Nodes[0]);
                    if (prefab.Prefab.PrefabNodes == null)
                    {
                        continue;
                    }
                    var mapPointOrigin = prefab.Prefab.PrefabNodes[prefab.Origin];

                    var rot = (float)(originNode.Rotation - Math.PI -
                                      Math.Atan2(mapPointOrigin.RotZ, mapPointOrigin.RotX) + Math.PI / 2);

                    var prefabstartX = originNode.X - mapPointOrigin.X;
                    var prefabStartZ = originNode.Z - mapPointOrigin.Z;
                    foreach (var spawnPoint in prefab.Prefab.SpawnPoints)
                    {
                        var newPoint = RenderHelper.RotatePoint(prefabstartX + spawnPoint.X, prefabStartZ + spawnPoint.Z, rot,
                                                                originNode.X, originNode.Z);

                        Bitmap b = null;

                        switch (spawnPoint.Type)
                        {
                        case TsSpawnPointType.GasPos:
                        {
                            var overlay = _mapper.LookupOverlay(ScsHash.StringToToken("gas_ico"));
                            b = overlay?.GetBitmap();
                            break;
                        }

                        case TsSpawnPointType.ServicePos:
                        {
                            var overlay = _mapper.LookupOverlay(ScsHash.StringToToken("service_ico"));
                            b = overlay?.GetBitmap();
                            break;
                        }

                        case TsSpawnPointType.WeightStationPos:
                        {
                            var overlay = _mapper.LookupOverlay(ScsHash.StringToToken("weigh_station_ico"));
                            b = overlay?.GetBitmap();
                            break;
                        }

                        case TsSpawnPointType.TruckDealerPos:
                        {
                            var overlay = _mapper.LookupOverlay(ScsHash.StringToToken("dealer_ico"));
                            b = overlay?.GetBitmap();
                            break;
                        }

                        case TsSpawnPointType.BuyPos:
                        {
                            var overlay = _mapper.LookupOverlay(ScsHash.StringToToken("garage_large_ico"));
                            b = overlay?.GetBitmap();
                            break;
                        }

                        case TsSpawnPointType.RecruitmentPos:
                        {
                            var overlay = _mapper.LookupOverlay(ScsHash.StringToToken("recruitment_ico"));
                            b = overlay?.GetBitmap();
                            break;
                        }
                        }
                        if (b != null)
                        {
                            g.DrawImage(b, newPoint.X - b.Width / 2f, newPoint.Y - b.Height / 2f, b.Width, b.Height);
                        }
                    }

                    var lastId = -1;
                    foreach (var triggerPoint in prefab.Prefab.TriggerPoints) // trigger points in prefabs: garage, hotel, ...
                    {
                        var newPoint = RenderHelper.RotatePoint(prefabstartX + triggerPoint.X, prefabStartZ + triggerPoint.Z, rot,
                                                                originNode.X, originNode.Z);

                        if (triggerPoint.TriggerId == lastId)
                        {
                            continue;
                        }
                        lastId = (int)triggerPoint.TriggerId;

                        if (triggerPoint.TriggerActionToken == ScsHash.StringToToken("hud_parking")) // parking trigger
                        {
                            var    overlay = _mapper.LookupOverlay(ScsHash.StringToToken("parking_ico"));
                            Bitmap b       = overlay?.GetBitmap();

                            if (b != null)
                            {
                                g.DrawImage(b, newPoint.X - b.Width / 2f, newPoint.Y - b.Height / 2f, b.Width, b.Height);
                            }
                        }
                    }
                }

                var triggers = _mapper.Triggers.Where(item =>
                                                      item.X >= startPoint.X - itemDrawMargin && item.X <= endPoint.X + itemDrawMargin && item.Z >= startPoint.Y - itemDrawMargin &&
                                                      item.Z <= endPoint.Y + itemDrawMargin && !item.Hidden)
                               .ToList();

                foreach (var triggerItem in triggers) // TODO: Scaling
                {
                    Bitmap b = triggerItem.Overlay?.GetBitmap();
                    if (b != null)
                    {
                        g.DrawImage(b, triggerItem.X, triggerItem.Z, b.Width, b.Height);
                    }
                }

                var ferryItems = _mapper.FerryConnections.Where(item =>
                                                                item.X >= startPoint.X - itemDrawMargin && item.X <= endPoint.X + itemDrawMargin && item.Z >= startPoint.Y - itemDrawMargin &&
                                                                item.Z <= endPoint.Y + itemDrawMargin)
                                 .ToList();

                foreach (var ferryItem in ferryItems) // TODO: Scaling
                {
                    Bitmap b = ferryItem.Overlay?.GetBitmap();
                    if (b != null)
                    {
                        g.DrawImage(b, ferryItem.X, ferryItem.Z, b.Width, b.Height);
                    }
                }
            }
            var mapOverlay2Time = DateTime.Now.Ticks - mapOverlay2StartTime;

            var cityStartTime = DateTime.Now.Ticks;

            if (renderFlags.IsActive(RenderFlags.CityNames)) // TODO: Fix position and scaling
            {
                var cities = _mapper.Cities.Where(item => !item.Hidden).ToList();

                var cityFont = new Font("Arial", 100 + zoomCaps[zoomIndex] / 100, FontStyle.Bold);

                foreach (var city in cities)
                {
                    var name = city.City.GetLocalizedName(_mapper.SelectedLocalization);

                    var node   = _mapper.GetNodeByUid(city.NodeUid);
                    var coords = (node == null) ? new PointF(city.X, city.Z) : new PointF(node.X, node.Z);
                    if (city.City.XOffsets.Count > zoomIndex && city.City.YOffsets.Count > zoomIndex)
                    {
                        coords.X += city.City.XOffsets[zoomIndex] / (scale * zoomCaps[zoomIndex]);
                        coords.Y += city.City.YOffsets[zoomIndex] / (scale * zoomCaps[zoomIndex]);
                    }

                    var textSize = g.MeasureString(name, cityFont);
                    g.DrawString(name, cityFont, _cityShadowColor, coords.X + 2, coords.Y + 2);
                    g.DrawString(name, cityFont, palette.CityName, coords.X, coords.Y);
                }
                cityFont.Dispose();
            }
            var cityTime = DateTime.Now.Ticks - cityStartTime;

            g.ResetTransform();
            var elapsedTime = DateTime.Now.Ticks - startTime;

            if (renderFlags.IsActive(RenderFlags.TextOverlay))
            {
                g.DrawString(
                    $"DrawTime: {elapsedTime / TimeSpan.TicksPerMillisecond} ms, x: {startPoint.X}, y: {startPoint.Y}, scale: {scale}",
                    _defaultFont, Brushes.WhiteSmoke, 5, 5);

                //g.FillRectangle(new SolidBrush(Color.FromArgb(100, 0, 0, 0)), 5, 20, 150, 150);
                //g.DrawString($"Road: {roadTime / TimeSpan.TicksPerMillisecond}ms", _defaultFont, Brushes.White, 10, 40);
                //g.DrawString($"Prefab: {prefabTime / TimeSpan.TicksPerMillisecond}ms", _defaultFont, Brushes.White, 10, 55);
                //g.DrawString($"Ferry: {ferryTime / TimeSpan.TicksPerMillisecond}ms", _defaultFont, Brushes.White, 10, 70);
                //g.DrawString($"MapOverlay: {mapOverlayTime / TimeSpan.TicksPerMillisecond}ms", _defaultFont, Brushes.White, 10, 85);
                //g.DrawString($"MapOverlay2: {mapOverlay2Time / TimeSpan.TicksPerMillisecond}ms", _defaultFont, Brushes.White, 10, 100);
                //g.DrawString($"MapArea: {mapAreaTime / TimeSpan.TicksPerMillisecond}ms", _defaultFont, Brushes.White, 10, 115);
                //g.DrawString($"City: {cityTime / TimeSpan.TicksPerMillisecond}ms", _defaultFont, Brushes.White, 10, 130);
            }
        }