Skip to content

Commit dad8ceb

Browse files
committed
fix: avoid city map visually overlapping the active lot in many cases
1 parent e60147f commit dad8ceb

3 files changed

Lines changed: 169 additions & 49 deletions

File tree

TSOClient/tso.client/Rendering/City/CityFoliage.cs

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using FSO.Common.Utils;
1+
using FSO.Common.Domain.Realestate;
2+
using FSO.Common.Utils;
23
using FSO.Content.Model;
34
using FSO.Files.RC;
45
using Microsoft.Xna.Framework;
@@ -46,14 +47,16 @@ private readonly struct TreeGroup(string name, int index, int count)
4647
public readonly int Count = count;
4748
}
4849

49-
public int ChunkSize = 16;
50+
public const int ChunkSize = 16;
5051
public CityMap MapData;
5152
public Dictionary<int, CityFoliageChunk> Chunks = new Dictionary<int, CityFoliageChunk>();
5253

5354
public DGRP3DVert[][] TreeVerts;
5455
public int[][] TreeInds;
5556
public readonly Matrix[] RotationMatrices;
5657

58+
private uint ActiveLocation;
59+
5760
private readonly TreeGroup[] TreeGroups =
5861
[
5962
new("pine", 0, 4), //4 models
@@ -144,7 +147,7 @@ public void InvalidateChunks(Rectangle rect)
144147
var x = i % 32;
145148
var y = i / 32;
146149

147-
var chunkRect = new Rectangle(x * 16, y * 16, 16, 16);
150+
var chunkRect = new Rectangle(x * ChunkSize, y * ChunkSize, ChunkSize, ChunkSize);
148151

149152
if (rect.Intersects(chunkRect))
150153
{
@@ -153,25 +156,46 @@ public void InvalidateChunks(Rectangle rect)
153156
}
154157
}
155158

159+
private void SetActiveLocation(uint location)
160+
{
161+
if (ActiveLocation != location)
162+
{
163+
foreach (var chunk in Chunks.Values)
164+
{
165+
uint filtered = chunk.FilterActiveLocation(location);
166+
167+
if (filtered != chunk.ActiveLocation)
168+
{
169+
chunk.ActiveLocation = filtered;
170+
chunk.Dirty = true;
171+
}
172+
}
173+
174+
ActiveLocation = location;
175+
}
176+
}
177+
156178
public void Draw(Terrain terrain, GraphicsDevice gd, CityContent content, Effect VertexShader, Effect PixelShader, int passIndex, int size, BoundingFrustum frustrum)
157179
{
158180
var camPos = terrain.Camera.CalculateR();
181+
SetActiveLocation(terrain.ActiveLocation);
159182

160-
var cx = (int)Math.Round(camPos.X / 16);
161-
var cy = (int)Math.Round(camPos.Y / 16);
183+
var cx = (int)Math.Round(camPos.X / ChunkSize);
184+
var cy = (int)Math.Round(camPos.Y / ChunkSize);
162185

163-
var invalid = Chunks.Keys.Where(i =>
186+
var invalid = Chunks.Where(chunkPair =>
164187
{
165-
var x = i % 32;
166-
var y = i / 32;
188+
var x = chunkPair.Value.X;
189+
var y = chunkPair.Value.Y;
190+
167191
return (x < cx - 2) || (x > cx + 2) || (y < cy - 2) || (y > cy + 2);
168192
}).ToList();
169193

170194
foreach (var c in invalid)
171195
{
172-
var chunk = Chunks[c];
196+
var chunk = c.Value;
173197
chunk.Dispose();
174-
Chunks.Remove(c);
198+
Chunks.Remove(c.Key);
175199
}
176200

177201
gd.RasterizerState = RasterizerState.CullNone;
@@ -225,7 +249,7 @@ public void Draw(Terrain terrain, GraphicsDevice gd, CityContent content, Effect
225249
}
226250
}
227251

228-
private (DGRP3DVert[], int[]) GetChunkData(int x, int y, HashSet<int> noTrees)
252+
private (DGRP3DVert[], int[]) GetChunkData(int x, int y, HashSet<int> noTrees, uint activeLocation)
229253
{
230254
var verts = new List<DGRP3DVert>();
231255
var inds = new List<int>();
@@ -242,13 +266,16 @@ public void Draw(Terrain terrain, GraphicsDevice gd, CityContent content, Effect
242266
var forestDensityData = MapData.ForestDensityData;
243267
var roadData = MapData.RoadData;
244268

269+
var locationCoords = MapCoordinates.Unpack(activeLocation);
270+
var treeCut = activeLocation == 0 ? Rectangle.Empty : new Rectangle(locationCoords.X - 1, locationCoords.Y - 1, 3, 3);
271+
245272
for (int oy = starty; oy < endy; oy++)
246273
{
247274
for (int ox = startx; ox < endx; ox++)
248275
{
249276
var ind = oy * 512 + ox;
250277
var forestType = forestTypeData[ind];
251-
if (forestType != ForestType.NULL && !noTrees.Contains(ind))
278+
if (forestType != ForestType.NULL && !noTrees.Contains(ind) && !treeCut.Contains(ox, oy))
252279
{
253280
if (forestType == 0 && terrainTypeData[ind] == TerrainType.SNOW) forestType = ForestType.SNOW;
254281
var densityN = ((forestDensityData[ind] * 4) / 255);
@@ -338,11 +365,13 @@ private void RegenerateChunk(CityFoliageChunk chunk, GraphicsDevice gd, int x, i
338365
return;
339366
}
340367

368+
chunk.ActiveLocation = ActiveLocation;
369+
341370
chunk.Regenerating = true;
342371

343372
Task.Run(() =>
344373
{
345-
var (verts, inds) = GetChunkData(x, y, noTrees);
374+
var (verts, inds) = GetChunkData(x, y, noTrees, chunk.ActiveLocation);
346375
GameThread.NextUpdate(state =>
347376
{
348377
if (verts.Length > 0 && !chunk.Dead)
@@ -385,7 +414,7 @@ public CityFoliageChunk GenerateChunk(GraphicsDevice gd, int x, int y, HashSet<i
385414
{
386415
var chunk = new CityFoliageChunk
387416
{
388-
Bounds = new BoundingBox(new Vector3(x * ChunkSize, 0, y * ChunkSize), new Vector3((x + 1) * 32, 255 / 12f, (y + 1) * 32))
417+
Bounds = new BoundingBox(new Vector3(x * ChunkSize, 0, y * ChunkSize), new Vector3((x + 1) * 32, 255 / 12f, (y + 1) * 32)),
389418
};
390419

391420
RegenerateChunk(chunk, gd, x, y, noTrees);
@@ -416,11 +445,31 @@ public class CityFoliageChunk
416445
public IndexBuffer Indices;
417446
public BoundingBox Bounds;
418447

448+
/// <summary>
449+
/// Properties around the active location have their city view trees removed to avoid overlapping lot graphics.
450+
/// If the active location doesn't overlap this chunk, it's set to 0.
451+
/// </summary>
452+
public uint ActiveLocation;
453+
419454
public bool Dirty;
420455
public bool Regenerating;
421456

422457
public bool Dead;
423458

459+
public uint FilterActiveLocation(uint location)
460+
{
461+
if (location == 0)
462+
{
463+
return 0;
464+
}
465+
466+
var coords = MapCoordinates.Unpack(location);
467+
468+
var chunkRect = new Rectangle(X * 16, Y * 16, 16, 16);
469+
470+
return chunkRect.Contains(coords.X, coords.Y) ? location : 0;
471+
}
472+
424473
public bool ShouldRegenerate()
425474
{
426475
if (Dirty && !Regenerating)

TSOClient/tso.client/Rendering/City/CityGeometry.cs

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
1-
using FSO.Common.Utils;
1+
using FSO.Common.Domain.Realestate;
2+
using FSO.Common.Utils;
23
using FSO.Content.Model;
34
using Microsoft.Xna.Framework;
45
using Microsoft.Xna.Framework.Graphics;
5-
using System;
6-
using System.Collections.Generic;
76
using System.Runtime.CompilerServices;
8-
using System.Threading.Tasks;
97

108
namespace FSO.Client.Rendering.City
119
{
10+
public readonly record struct CitySliceKey
11+
{
12+
public readonly int SliceID;
13+
public readonly Rectangle? FlattenRect;
14+
15+
public CitySliceKey(int sliceID, uint lotId)
16+
{
17+
SliceID = sliceID;
18+
FlattenRect = null;
19+
20+
if (lotId != 0)
21+
{
22+
// Try and build a flatten rect around the target lot.
23+
// If surrounding lots are disabled, it only affects the current lot.
24+
// If they're enabled, it affects the surrounding lots too.
25+
26+
var pos = MapCoordinates.Unpack(lotId);
27+
28+
FlattenRect = new Rectangle(pos.ToPoint(), new Point(1));
29+
}
30+
}
31+
}
32+
1233
public class CityGeometry
1334
{
1435
//draw order:
@@ -25,7 +46,7 @@ public class CityGeometry
2546
public int Width;
2647
public int Height;
2748
public int Ready = -1;
28-
public int CurrentSlice = -1;
49+
public CitySliceKey? CurrentSlice = null;
2950

3051
private bool MeshRegenInProgress;
3152
private bool MeshDirty;
@@ -529,14 +550,42 @@ public void RegenMeshVerts(GraphicsDevice gd, bool async)
529550
}
530551

531552
[MethodImpl(MethodImplOptions.AggressiveInlining)]
532-
private int O(int x, int y, int minx, int maxx)
553+
private static int O(int x, int y, int minx, int maxx)
533554
{
534555
return (Math.Max(0, Math.Min(511, y)) * 512 + Math.Max(minx, Math.Min(maxx, x)));
535556
}
536557

537-
public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, int cpos)
558+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
559+
private static float GetContinuity(int x, int y, in Rectangle range, in Rectangle? flattenRect)
538560
{
539-
CurrentSlice = cpos;
561+
if (x <= range.X || x >= range.Right || y <= range.Y || y >= range.Bottom)
562+
{
563+
return -1;
564+
}
565+
566+
if (flattenRect.HasValue)
567+
{
568+
Rectangle rect = flattenRect.Value;
569+
570+
if (x >= rect.X && x <= rect.Right && y >= rect.Y && y <= rect.Bottom)
571+
{
572+
return -1;
573+
}
574+
}
575+
576+
return 0;
577+
}
578+
579+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
580+
private static float Lerp(float a, float b, float t)
581+
{
582+
return a * (1 - t) + b * t;
583+
}
584+
585+
public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle range, int subdiv, CitySliceKey slice)
586+
{
587+
var cpos = slice.SliceID;
588+
CurrentSlice = slice;
540589
var indices = new List<int>[5];
541590
var vertices = new List<TLayerVertex>[5];
542591

@@ -560,11 +609,8 @@ public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, i
560609
float subd1f = 1f / subdiv;
561610
int vertCount = subd1 * subd1;
562611

563-
if (range.HasValue)
564-
{
565-
yStart = range.Value.Y;
566-
yEnd = range.Value.Bottom;
567-
}
612+
yStart = range.Y;
613+
yEnd = range.Bottom;
568614

569615
Task.Run(() =>
570616
{
@@ -599,11 +645,8 @@ public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, i
599645
xStart -= fadeRange;
600646
xEnd += fadeRange;
601647

602-
if (range.HasValue)
603-
{
604-
xStart = Math.Max(range.Value.X, xStart);
605-
xEnd = Math.Min(range.Value.Right, xEnd);
606-
}
648+
xStart = Math.Max(range.X, xStart);
649+
xEnd = Math.Min(range.Right, xEnd);
607650

608651
for (int j = xStart; j < xEnd; j++)
609652
{ //where the magic happens
@@ -639,11 +682,12 @@ public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, i
639682
var normalRoad = roadByte & 15;
640683
var cornerRoad = roadByte >> 4;
641684

642-
var yEdge = (j == range.Value.X) ? -1f : 0f;
643-
var yEdge2 = (j == range.Value.Right - 1) ? -1f : 0f;
685+
// Also enforce continuity when within the FlattenRect
644686

645-
var xEdge = (i == range.Value.Y) ? -1f : 0f;
646-
var xEdge2 = (i == range.Value.Bottom - 1) ? -1f : 0f;
687+
var cont00 = GetContinuity(j, i, range, in slice.FlattenRect);
688+
var cont10 = GetContinuity(j + 1, i, range, in slice.FlattenRect);
689+
var cont01 = GetContinuity(j, i + 1, range, in slice.FlattenRect);
690+
var cont11 = GetContinuity(j + 1, i + 1, range, in slice.FlattenRect);
647691

648692
Span<float> d =
649693
[
@@ -658,15 +702,18 @@ public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, i
658702
var yi = 0f;
659703
for (int y = 0; y < subd1; y++)
660704
{
661-
var lXE = (yi * xEdge2) + ((1 - yi) * xEdge);
662705
var xi = 0f;
663706
for (int x = 0; x < subd1; x++)
664707
{
708+
var yEdge = Lerp(cont00, cont01, yi);
709+
var yEdge2 = Lerp(cont10, cont11, yi);
710+
665711
float y1 = Cubic(d[0], d[1], d[2], d[3], yi, yEdge);
666712
float y2 = Cubic(d[4], d[5], d[6], d[7], yi, yEdge);
667713
float y3 = Cubic(d[8], d[9], d[10], d[11], yi, yEdge2);
668714
float y4 = Cubic(d[12], d[13], d[14], d[15], yi, yEdge2);
669715

716+
var lXE = Lerp(yEdge, yEdge2, xi);
670717
var h = Cubic(y1, y2, y3, y4, xi, lXE);
671718

672719
var lerpNX = Vector3.Lerp(norm1, norm2, xi);

0 commit comments

Comments
 (0)