public TsRoadItem(TsSector sector, int startOffset) : base(sector, startOffset) { Valid = true; var fileOffset = startOffset + 0x34; // Set position at start of flags Hidden = (Sector.Stream[fileOffset += 0x03] & 0x02) != 0; RoadLook = Sector.Mapper.LookupRoadLook(BitConverter.ToUInt64(Sector.Stream, fileOffset += 0x06)); if (RoadLook == null) { Valid = false; Log.Msg($"Could not find RoadLook with id: {BitConverter.ToUInt64(Sector.Stream, fileOffset):X}, " + $"in {Path.GetFileName(Sector.FilePath)} @ {fileOffset}"); } if (Sector.Version >= Common.BaseFileVersion130) { StartNodeUid = BitConverter.ToUInt64(Sector.Stream, fileOffset += 0x08 + 0xA4); EndNodeUid = BitConverter.ToUInt64(Sector.Stream, fileOffset += 0x08); fileOffset += 0x08 + 0x04; } else { StartNodeUid = BitConverter.ToUInt64(Sector.Stream, fileOffset += 0x08 + 0x50); EndNodeUid = BitConverter.ToUInt64(Sector.Stream, fileOffset += 0x08); var stampCount = BitConverter.ToInt32(Sector.Stream, fileOffset += 0x08 + 0x134); var vegetationSphereCount = BitConverter.ToInt32(Sector.Stream, fileOffset += 0x04 + (StampBlockSize * stampCount)); fileOffset += 0x04 + (VegetationSphereBlockSize * vegetationSphereCount); } BlockSize = fileOffset - startOffset; }
public TsRoadItem(TsSector sector, int startOffset) : base(sector, startOffset) { Valid = true; var fileOffset = startOffset + 0x34; // Set position at start of flags var dlcGuardCount = (Sector.Mapper.IsEts2) ? Common.Ets2DlcGuardCount: Common.AtsDlcGuardCount; Hidden = MemoryHelper.ReadInt8(Sector.Stream, fileOffset + 0x06) > dlcGuardCount || (MemoryHelper.ReadUint8(Sector.Stream, fileOffset + 0x03) & 0x02) != 0; RoadLook = Sector.Mapper.LookupRoadLook(MemoryHelper.ReadUInt64(Sector.Stream, fileOffset += 0x09)); if (RoadLook == null) { Valid = false; Log.Msg($"Could not find RoadLook with id: {MemoryHelper.ReadUInt64(Sector.Stream, fileOffset):X}, " + $"in {Path.GetFileName(Sector.FilePath)} @ {fileOffset}"); } if (Sector.Version >= Common.BaseFileVersion130) { StartNodeUid = MemoryHelper.ReadUInt64(Sector.Stream, fileOffset += 0x08 + 0xA4); EndNodeUid = MemoryHelper.ReadUInt64(Sector.Stream, fileOffset += 0x08); fileOffset += 0x08 + 0x04; } else { StartNodeUid = MemoryHelper.ReadUInt64(Sector.Stream, fileOffset += 0x08 + 0x50); EndNodeUid = MemoryHelper.ReadUInt64(Sector.Stream, fileOffset += 0x08); var stampCount = MemoryHelper.ReadInt32(Sector.Stream, fileOffset += 0x08 + 0x134); var vegetationSphereCount = MemoryHelper.ReadInt32(Sector.Stream, fileOffset += 0x04 + (StampBlockSize * stampCount)); fileOffset += 0x04 + (VegetationSphereBlockSize * vegetationSphereCount); } BlockSize = fileOffset - startOffset; }
private static void AddPoints(TsRoadItem road) { if (!road.HasPoints()) { var startNode = road.GetStartNode(); var endNode = road.GetEndNode(); 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); } }
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); }
private void ParseRoadLookFiles() { var worldDirectory = Rfs.GetDirectory("def/world"); if (worldDirectory == null) { Log.Msg("Could not read 'def/world' dir"); return; } var roadLookFiles = worldDirectory.GetFiles("road_look"); if (roadLookFiles == null) { Log.Msg("Could not read road look files"); return; } foreach (var roadLookFile in roadLookFiles) { var data = roadLookFile.Entry.Read(); var lines = Encoding.UTF8.GetString(data).Split('\n'); TsRoadLook roadLook = null; foreach (var line in lines) { if (line.Contains(":") && roadLook != null) { var value = line.Substring(line.IndexOf(':') + 1).Trim(); var key = line.Substring(0, line.IndexOf(':')).Trim(); switch (key) { case "lanes_left[]": roadLook.LanesLeft.Add(value); break; case "lanes_right[]": roadLook.LanesRight.Add(value); break; case "road_offset": float.TryParse(value.Replace('.', ','), out roadLook.Offset); break; } } if (line.Contains("road_look")) { roadLook = new TsRoadLook(ScsHash.StringToToken(line.Split('.')[1].Trim('{').Trim())); } if (line.Contains("}") && roadLook != null) { if (roadLook.Token != 0 && !_roadLookup.ContainsKey(roadLook.Token)) { _roadLookup.Add(roadLook.Token, roadLook); roadLook = null; } } } } }
private void ParseRoadLookFiles() { var worldDirectory = Rfs.GetDirectory("def/world"); if (worldDirectory == null) { Log.Msg("Could not read 'def/world' dir"); return; } var roadLookFiles = worldDirectory.GetFiles("road_look"); if (roadLookFiles == null) { Log.Msg("Could not read road look files"); return; } foreach (var roadLookFile in roadLookFiles) { if (!roadLookFile.GetFileName().StartsWith("road")) { continue; } var data = roadLookFile.Entry.Read(); var lines = Encoding.UTF8.GetString(data).Split('\n'); TsRoadLook roadLook = null; foreach (var line in lines) { var(validLine, key, value) = SiiHelper.ParseLine(line); if (validLine) { if (key == "road_look") { roadLook = new TsRoadLook(ScsHash.StringToToken(SiiHelper.Trim(value.Split('.')[1].Trim('{')))); } if (roadLook == null) { continue; } if (key == "lanes_left[]") { roadLook.LanesLeft.Add(value); } else if (key == "lanes_right[]") { roadLook.LanesRight.Add(value); } else if (key == "road_offset") { roadLook.Offset = float.Parse(value, CultureInfo.InvariantCulture); } } if (line.Contains("}") && roadLook != null) { if (roadLook.Token != 0 && !_roadLookup.ContainsKey(roadLook.Token)) { _roadLookup.Add(roadLook.Token, roadLook); roadLook = null; } } } } }
/// <summary> /// Loads navigation inside TsItem objects /// </summary> private void LoadNavigation() { foreach (var prefab in Prefabs) { foreach (var nodeStr in prefab.Nodes) { var node = GetNodeByUid(nodeStr); TsItem road = null; TsNode precnode = node; TsItem precitem = prefab; TsNode nextnode; TsItem nextitem; List <TsItem> roads = new List <TsItem>(); var totalLength = 0.0f; if (node.ForwardItem != null && node.ForwardItem.Type == TsItemType.Road) { road = node.ForwardItem; } else if (node.BackwardItem != null && node.BackwardItem.Type == TsItemType.Road) { road = node.BackwardItem; } if (road != null) { int direction = 0; if (road.EndNodeUid == node.Uid) { direction = 1; } while (road != null && road.Type != TsItemType.Prefab && !road.Hidden) { var length = (float)Math.Sqrt(Math.Pow(GetNodeByUid(road.StartNodeUid).X - GetNodeByUid(road.EndNodeUid).X, 2) + Math.Pow(GetNodeByUid(road.StartNodeUid).Z - GetNodeByUid(road.EndNodeUid).Z, 2)); TsRoadItem roadObj = (TsRoadItem)road; totalLength += length / roadObj.RoadLook.GetWidth(); /*if (roadObj.RoadLook.IsHighway) totalLength += (length / 2) / roadObj.RoadLook.GetWidth(); * else if (roadObj.RoadLook.IsLocal) totalLength += (length / 1.75f) / roadObj.RoadLook.GetWidth(); * else if (roadObj.RoadLook.IsExpress) totalLength += (length / 1.25f) / roadObj.RoadLook.GetWidth(); * else length += length * 2;*/ roads.Add(road); if (GetNodeByUid(road.StartNodeUid) == precnode) { nextnode = GetNodeByUid(road.EndNodeUid); precnode = GetNodeByUid(road.EndNodeUid); } else { nextnode = GetNodeByUid(road.StartNodeUid); precnode = GetNodeByUid(road.StartNodeUid); } if (nextnode.BackwardItem == road || nextnode.BackwardItem == precitem) { nextitem = nextnode.ForwardItem; precitem = nextnode.ForwardItem; } else { nextitem = nextnode.BackwardItem; precitem = nextnode.BackwardItem; } road = nextitem; } if (road != null && !road.Hidden) { TsPrefabItem prevPrefab = (TsPrefabItem)prefab; TsPrefabItem nextPrefab = (TsPrefabItem)road; TsRoadLook look = ((TsRoadItem)roads.LastOrDefault()).RoadLook; if (prevPrefab.Hidden || nextPrefab.Hidden) { continue; } if (prevPrefab.Navigation.ContainsKey(nextPrefab) == false && (look.IsBidirectional() || direction == 0)) { prevPrefab.Navigation.Add(nextPrefab, new Tuple <float, List <TsItem> >(totalLength, roads)); } if (nextPrefab.Navigation.ContainsKey(prevPrefab) == false && (look.IsBidirectional() || direction == 1)) { var reverse = new List <TsItem>(roads); reverse.Reverse(); nextPrefab.Navigation.Add(prevPrefab, new Tuple <float, List <TsItem> >(totalLength, reverse)); } } } else if (node.ForwardItem != null && node.BackwardItem != null) { TsPrefabItem forwardPrefab = (TsPrefabItem)node.ForwardItem; TsPrefabItem backwardPrefab = (TsPrefabItem)node.BackwardItem; if (forwardPrefab.Hidden || backwardPrefab.Hidden) { continue; } if (forwardPrefab.Navigation.ContainsKey(backwardPrefab) == false) { forwardPrefab.Navigation.Add(backwardPrefab, new Tuple <float, List <TsItem> >(0, null)); } if (backwardPrefab.Navigation.ContainsKey(forwardPrefab) == false) { backwardPrefab.Navigation.Add(forwardPrefab, new Tuple <float, List <TsItem> >(0, null)); } } } } Dictionary <ulong, TsPrefabItem> ferryToPrefab = new Dictionary <ulong, TsPrefabItem>(); foreach (var port in FerryConnections) { float min = float.MaxValue; TsPrefabItem closerPrefab = null; foreach (var prefab in Prefabs) { float distance = (float)Math.Sqrt(Math.Pow(port.X - prefab.X, 2) + Math.Pow(port.Z - prefab.Z, 2)); if (distance < min && prefab.Navigation.Count > 1 && !prefab.Hidden) { min = distance; closerPrefab = prefab; } } ferryToPrefab[port.FerryPortId] = closerPrefab; } foreach (var port in FerryConnections) { foreach (var connection in LookupFerryConnection(port.FerryPortId)) { var ports = new List <TsItem>(); ports.Add(FerryPortbyId[connection.StartPortToken]); ports.Add(FerryPortbyId[connection.EndPortToken]); ferryToPrefab[connection.StartPortToken].Navigation.Add(ferryToPrefab[connection.EndPortToken], new Tuple <float, List <TsItem> >(connection.Distance, ports)); ports.Reverse(); ferryToPrefab[connection.EndPortToken].Navigation.Add(ferryToPrefab[connection.StartPortToken], new Tuple <float, List <TsItem> >(connection.Distance, ports)); } } }
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); } }