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; 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 zoomIndex = GetZoomIndex(clip, scale); var endPoint = new PointF(startPoint.X + clip.Width / scale, startPoint.Y + clip.Height / scale); var ferryStartTime = DateTime.Now.Ticks; var ferryPen = new Pen(palette.FerryLines, 50) { DashPattern = new[] { 10f, 10f } }; if ((renderFlags & RenderFlags.FerryConnections) != RenderFlags.None) { 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) { 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 = 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 = 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 = 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()); } } } var ferryTime = DateTime.Now.Ticks - ferryStartTime; var mapAreaStartTime = DateTime.Now.Ticks; if ((renderFlags & RenderFlags.MapAreas) != RenderFlags.None) { 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 & RenderFlags.Prefabs) != RenderFlags.None) { 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 = 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 = GetCornerCoords(prefabstartX + mapPoint.X, prefabStartZ + mapPoint.Z, (4.5f * mapPointLaneCount + mapPoint.LaneOffset) / 2f, roadYaw + Math.PI / 2); cornerCoords.Add(RotatePoint(coords.X, coords.Y, rot, originNode.X, originNode.Z)); coords = GetCornerCoords(prefabstartX + neighbourPoint.X, prefabStartZ + neighbourPoint.Z, (4.5f * neighbourLaneCount + neighbourPoint.LaneOffset) / 2f, roadYaw + Math.PI / 2); cornerCoords.Add(RotatePoint(coords.X, coords.Y, rot, originNode.X, originNode.Z)); coords = GetCornerCoords(prefabstartX + neighbourPoint.X, prefabStartZ + neighbourPoint.Z, (4.5f * neighbourLaneCount + mapPoint.LaneOffset) / 2f, roadYaw - Math.PI / 2); cornerCoords.Add(RotatePoint(coords.X, coords.Y, rot, originNode.X, originNode.Z)); coords = GetCornerCoords(prefabstartX + mapPoint.X, prefabStartZ + mapPoint.Z, (4.5f * mapPointLaneCount + mapPoint.LaneOffset) / 2f, roadYaw - Math.PI / 2); cornerCoords.Add(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 & RenderFlags.Roads) != RenderFlags.None) { 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(); g.DrawCurve(new Pen(palette.Road, roadWidth), road.GetPoints()?.ToArray()); } } var roadTime = DateTime.Now.Ticks - roadStartTime; var mapOverlayStartTime = DateTime.Now.Ticks; if ((renderFlags & RenderFlags.MapOverlays) != RenderFlags.None) { 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 & RenderFlags.MapOverlays) != RenderFlags.None) { 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 { Bitmap b = companyItem.Overlay?.GetBitmap(); if (b != null) { g.DrawImage(b, companyItem.X, companyItem.Z, 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 = RotatePoint(prefabstartX + spawnPoint.X, prefabStartZ + spawnPoint.Z, rot, originNode.X, originNode.Z); Bitmap b = null; switch (spawnPoint.Type) { case TsSpawnPointType.Fuel: { var overlay = _mapper.LookupOverlay(0x11C686A54F); b = overlay?.GetBitmap(); break; } case TsSpawnPointType.Service: { var overlay = _mapper.LookupOverlay(0x2358E7493388A97); b = overlay?.GetBitmap(); break; } case TsSpawnPointType.WeightStation: { var overlay = _mapper.LookupOverlay(0xD50E1058FBBF179F); b = overlay?.GetBitmap(); break; } case TsSpawnPointType.TruckDealer: { var overlay = _mapper.LookupOverlay(0xEE210C8438914); b = overlay?.GetBitmap(); break; } case TsSpawnPointType.GarageOutdoor: { var overlay = _mapper.LookupOverlay(0x4572831B4D58CC5B); b = overlay?.GetBitmap(); break; } case TsSpawnPointType.Recruitment: { var overlay = _mapper.LookupOverlay(0x1E18DD7A560F3E5A); 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 = 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, 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 & RenderFlags.CityNames) != RenderFlags.None) // 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.Name; if (city.City.NameLocalized != string.Empty) { var localName = _mapper.GetLocalizedName(city.City.NameLocalized); if (localName != null) { name = localName; } } 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, new SolidBrush(Color.FromArgb(210, 0, 0, 0)), coords.X + 2, coords.Y + 2); g.DrawString(name, cityFont, palette.CityName, coords.X, coords.Y); } } var cityTime = DateTime.Now.Ticks - cityStartTime; g.ResetTransform(); var elapsedTime = DateTime.Now.Ticks - startTime; if ((renderFlags & RenderFlags.TextOverlay) != RenderFlags.None) { 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); } }
public void Render(Graphics g, Rectangle clip, float baseScale, PointF pos, MapPalette palette, RenderFlags renderFlags = RenderFlags.All) { 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; g.SmoothingMode = SmoothingMode.AntiAlias; 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; } if ((renderFlags & RenderFlags.FerryConnections) != RenderFlags.None) { 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 pen = new Pen(palette.FerryLines, 50 * scaleX) { DashPattern = new[] { 10f, 10f } }; g.DrawCurve(pen, newPoints.ToArray()); } } } if ((renderFlags & RenderFlags.MapAreas) != RenderFlags.None) { var mapAreas = _mapper.MapAreas.Where(item => item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 && item.Z <= endY + 1500 && !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 - startX) * scaleX, (node.Z - startY) * scaleY)); } 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 prefabs = _mapper.Prefabs.Where(item => item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 && item.Z <= endY + 1500 && !item.Hidden) .ToList(); if ((renderFlags & RenderFlags.Prefabs) != RenderFlags.None) { List <TsPrefabLook> drawingQueue = new List <TsPrefabLook>(); foreach (var prefabItem in prefabs) // TODO: Road Width { var originNode = _mapper.GetNodeByUid(prefabItem.Nodes[0]); if (prefabItem.Prefab.PrefabNodes == null) { continue; } 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 = 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); 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 }; drawingQueue.Add(prefabLook); continue; } 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 cornerCoords = new List <PointF>(); var coords = GetCornerCoords((prefabStartX + mapPoint.X - startX) * scaleX, (prefabStartZ + mapPoint.Z - startY) * scaleY, (4.5f * mapPoint.LaneCount + mapPoint.LaneOffset) / 2f * scaleX, roadYaw + Math.PI / 2); cornerCoords.Add(RotatePoint(coords.X, coords.Y, rot, (originNode.X - startX) * scaleX, (originNode.Z - startY) * scaleY)); coords = GetCornerCoords((prefabStartX + neighbourPoint.X - startX) * scaleX, (prefabStartZ + neighbourPoint.Z - startY) * scaleY, (4.5f * neighbourPoint.LaneCount + neighbourPoint.LaneOffset) / 2f * scaleX, roadYaw + Math.PI / 2); cornerCoords.Add(RotatePoint(coords.X, coords.Y, rot, (originNode.X - startX) * scaleX, (originNode.Z - startY) * scaleY)); coords = GetCornerCoords((prefabStartX + neighbourPoint.X - startX) * scaleX, (prefabStartZ + neighbourPoint.Z - startY) * scaleY, (4.5f * neighbourPoint.LaneCount + mapPoint.LaneOffset) / 2f * scaleX, roadYaw - Math.PI / 2); cornerCoords.Add(RotatePoint(coords.X, coords.Y, rot, (originNode.X - startX) * scaleX, (originNode.Z - startY) * scaleY)); coords = GetCornerCoords((prefabStartX + mapPoint.X - startX) * scaleX, (prefabStartZ + mapPoint.Z - startY) * scaleY, (4.5f * mapPoint.LaneCount + mapPoint.LaneOffset) / 2f * scaleX, roadYaw - Math.PI / 2); cornerCoords.Add(RotatePoint(coords.X, coords.Y, rot, (originNode.X - startX) * scaleX, (originNode.Z - startY) * scaleY)); TsPrefabLook prefabLook = new TsPrefabPolyLook(cornerCoords) { Color = palette.PrefabRoad, ZIndex = 4, }; drawingQueue.Add(prefabLook); } } } foreach (var prefabLook in drawingQueue.OrderBy(p => p.ZIndex)) { prefabLook.Draw(g); } } if ((renderFlags & RenderFlags.Roads) != RenderFlags.None) { 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; g.DrawCurve(new Pen(palette.Road, roadWidth), points.ToArray()); } } if ((renderFlags & RenderFlags.MapOverlays) != RenderFlags.None) { var overlays = _mapper.MapOverlays.Where(item => item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 && item.Z <= endY + 1500 && !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 - startX) * scaleX, (overlayItem.Z - b.Height - startY) * scaleY, b.Width * 2 * scaleX, b.Height * 2 * scaleY); } } } if ((renderFlags & RenderFlags.MapOverlays) != RenderFlags.None) { var companies = _mapper.Companies.Where(item => item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 && item.Z <= endY + 1500 && !item.Hidden) .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 * 2 * scaleX, b.Height * 2 * scaleY); } } 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 = 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 * 2 * scaleX, b.Height * 2 * 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 * 2 * scaleX, b.Height * 2 * 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 * 2 * scaleX, b.Height * 2 * 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 * 2 * scaleX, b.Height * 2 * 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 * 2 * scaleX, b.Height * 2 * 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 * 2 * scaleX, b.Height * 2 * 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 * 2 * scaleX, b.Height * 2 * scaleY); } } } } var triggers = _mapper.Triggers.Where(item => item.X >= startX - 1500 && item.X <= endX + 1500 && item.Z >= startY - 1500 && item.Z <= endY + 1500 && !item.Hidden) .ToList(); foreach (var triggerItem in triggers) // TODO: Scaling { Bitmap b = triggerItem.Overlay?.GetBitmap(); if (b != null) { g.DrawImage(b, (triggerItem.X - startX) * scaleX, (triggerItem.Z - startY) * scaleY, b.Width * 2 * scaleX, b.Height * 2 * 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 * 2 * scaleX, b.Height * 2 * scaleY); } } } if ((renderFlags & RenderFlags.CityNames) != RenderFlags.None) { 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("Microsoft Sans Serif", 80 * scaleX, FontStyle.Bold); var name = city.City.Name; if (city.City.NameLocalized != string.Empty) { var localName = _mapper.GetLocalizedName(city.City.NameLocalized); if (localName != null) { name = localName; } } g.DrawString(name, cityFont, palette.CityName, (city.X - startX) * scaleX, (city.Z - startY) * scaleY); } } var elapsedTime = DateTime.Now.Ticks - startTime; if ((renderFlags & RenderFlags.TextOverlay) != RenderFlags.None) { //g.DrawString( // $"DrawTime: {elapsedTime / TimeSpan.TicksPerMillisecond} ms, x: {centerX}, y: {centerY}, scale: {baseScale}", // defaultFont, Brushes.WhiteSmoke, 5, 5); } }