Skip to content

Commit b95e285

Browse files
committed
Keep terrain overlay material mesh codes
1 parent a19ad07 commit b95e285

16 files changed

Lines changed: 174 additions & 55 deletions

src/PlateauResoniteLink/Application/Importing/CityGmlSurfaceMaterialResolver.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,11 @@ internal static MaterialBinding CreateMaterialBinding(
140140
{
141141
TerrainOverlayMaterialBinding? terrainOverlayMaterial = representativeSurface.Material.TerrainOverlay is null
142142
? null
143-
: new TerrainOverlayMaterialBinding(representativeSurface.Material.TerrainOverlay);
143+
: new TerrainOverlayMaterialBinding(
144+
ThirdRegionalMeshCode.TryParse(actualMeshCode, out ThirdRegionalMeshCode parsedActualMeshCode)
145+
? parsedActualMeshCode
146+
: representativeSurface.Material.TerrainOverlay.MeshCode,
147+
representativeSurface.Material.TerrainOverlay);
144148
ColorRgba baseColor = representativeSurface.Material.TerrainOverlay is null
145149
? ToContractColor(representativeSurface.Surface.BaseColor)
146150
: new ColorRgba(1.0, 1.0, 1.0, 1.0);

src/PlateauResoniteLink/Application/Importing/SceneImportContractTypes.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,22 @@ public enum MaterialReuseScope
200200

201201
public sealed record TerrainOverlayMaterialBinding
202202
{
203-
public TerrainOverlayMaterialBinding(TerrainTextureOverlay overlay)
203+
public TerrainOverlayMaterialBinding(
204+
ThirdRegionalMeshCode meshCode,
205+
TerrainTextureOverlay overlay)
204206
{
207+
if (!ThirdRegionalMeshCode.TryParse(meshCode.Value, out _))
208+
{
209+
throw new ArgumentException("Terrain overlay material mesh code must be a valid third regional mesh code.", nameof(meshCode));
210+
}
211+
212+
MeshCode = meshCode;
205213
Overlay = overlay ?? throw new ArgumentNullException(nameof(overlay));
206214
}
207215

208-
public TerrainTextureOverlay Overlay { get; init; }
216+
public ThirdRegionalMeshCode MeshCode { get; }
209217

210-
public ThirdRegionalMeshCode MeshCode => Overlay.MeshCode;
218+
public TerrainTextureOverlay Overlay { get; }
211219
}
212220

213221
public sealed record MaterialBinding(

src/PlateauResoniteLink/Targets/Resonite/ResonitePreparedTextureUploader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public static async Task<ResoniteUploadedTextureAssetSet> UploadAsync(
8080
foreach ((PreparedTerrainOverlayTextureReference texture, Task<SharedTerrainTextureAsset> importTask) in terrainTextureImportTasks)
8181
{
8282
SharedTerrainTextureAsset sharedTexture = await importTask;
83-
terrainTextureUrisByOverlay.Add(texture.Overlay, sharedTexture.TextureUri);
83+
terrainTextureUrisByOverlay.TryAdd(texture.Overlay, sharedTexture.TextureUri);
8484
terrainTexturePropertyBlockComponentsByMeshCode.TryAdd(texture.MeshCode, sharedTexture.MainTexturePropertyBlockComponent.Locator);
8585
}
8686

src/PlateauResoniteLink/Targets/Resonite/ResoniteQueuedCityObjectPreparation.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,12 @@ private async Task<PreparedCityObject> PrepareCityObjectAsync(
108108
progressReporter,
109109
cancellationToken);
110110
PreparedConstructionGeometry preparedGeometry = await geometryPreparationTask.WaitAsync(cancellationToken);
111-
Dictionary<TerrainTextureOverlay, GeneratedTerrainTexture> preparedTerrainTextureDataByOverlay = preparedTextures
112-
.OfType<PreparedTerrainOverlayTextureReference>()
113-
.ToDictionary(
114-
static texture => texture.Overlay,
115-
static texture => texture.GeneratedTerrainTexture);
111+
Dictionary<TerrainTextureOverlay, GeneratedTerrainTexture> preparedTerrainTextureDataByOverlay = [];
112+
foreach (PreparedTerrainOverlayTextureReference texture in preparedTextures.OfType<PreparedTerrainOverlayTextureReference>())
113+
{
114+
preparedTerrainTextureDataByOverlay.TryAdd(texture.Overlay, texture.GeneratedTerrainTexture);
115+
}
116+
116117
cityObject = ResoniteCityObjectPreparation.ApplyTerrainTextureCanvasUv(
117118
cityObject,
118119
preparedTerrainTextureDataByOverlay,

src/PlateauResoniteLink/Targets/Resonite/ResoniteQueuedTexturePreparer.cs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Globalization;
34
using System.Linq;
45
using System.Threading;
@@ -40,11 +41,11 @@ public async Task<PreparedTextureReference[]> PrepareAsync(
4041
ArgumentNullException.ThrowIfNull(routedClient);
4142
ArgumentNullException.ThrowIfNull(cityObject);
4243

43-
(ThirdRegionalMeshCode TerrainMeshCode, TerrainTextureOverlay TerrainOverlay)[] distinctTerrainOverlays = cityObject.Materials
44+
(ThirdRegionalMeshCode TerrainMeshCode, TerrainTextureOverlay TerrainOverlay)[] distinctTerrainOverlayBindings = cityObject.Materials
4445
.Select((material, materialIndex) => (Material: material, MaterialIndex: materialIndex))
4546
.Where(static entry => entry.Material.TerrainOverlayMaterial is not null)
4647
.Select(entry => (
47-
TerrainMeshCode: entry.Material.TerrainOverlayMaterial!.Overlay.MeshCode,
48+
TerrainMeshCode: entry.Material.TerrainOverlayMaterial!.MeshCode,
4849
TerrainOverlay: entry.Material.TerrainOverlayMaterial!.Overlay))
4950
.Distinct()
5051
.OrderBy(static entry => entry.TerrainMeshCode.Value, StringComparer.Ordinal)
@@ -53,31 +54,51 @@ public async Task<PreparedTextureReference[]> PrepareAsync(
5354
.ThenBy(static entry => entry.TerrainOverlay.GeographicBounds.MinLongitude)
5455
.ToArray();
5556

56-
Task<PreparedTextureReference?>[] terrainOverlayTexturePreparationTasks = distinctTerrainOverlays
57-
.Select(entry => PrepareTerrainOverlayTextureReferenceAsync(
57+
TerrainTextureOverlay[] distinctTerrainOverlays = distinctTerrainOverlayBindings
58+
.Select(static entry => entry.TerrainOverlay)
59+
.Distinct()
60+
.OrderBy(static overlay => overlay.PackageName, StringComparer.Ordinal)
61+
.ThenBy(static overlay => overlay.MeshCode.Value, StringComparer.Ordinal)
62+
.ThenBy(static overlay => overlay.GeographicBounds.MinLatitude)
63+
.ThenBy(static overlay => overlay.GeographicBounds.MinLongitude)
64+
.ToArray();
65+
66+
Task<(TerrainTextureOverlay Overlay, GeneratedTerrainTexture Texture)>[] terrainOverlayTexturePreparationTasks = distinctTerrainOverlays
67+
.Select(async overlay => (
68+
Overlay: overlay,
69+
Texture: await PrepareTerrainOverlayTextureAsync(
5870
state,
5971
routedClient,
6072
progressReporter,
73+
overlay,
74+
cancellationToken)))
75+
.ToArray();
76+
Task<PreparedTextureReference?>[] directTexturePreparationTasks = cityObject.Materials
77+
.Where(static material => material.TexturePayload is not null)
78+
.Select(PrepareDirectMaterialTextureReferenceAsync)
79+
.ToArray();
80+
await Task.WhenAll(terrainOverlayTexturePreparationTasks);
81+
PreparedTextureReference?[] preparedDirectTextureResults = await Task.WhenAll(directTexturePreparationTasks);
82+
Dictionary<TerrainTextureOverlay, GeneratedTerrainTexture> terrainTexturesByOverlay = terrainOverlayTexturePreparationTasks
83+
.Select(static task => task.Result)
84+
.ToDictionary(static result => result.Overlay, static result => result.Texture);
85+
PreparedTerrainOverlayTextureReference[] preparedTerrainTextureReferences = distinctTerrainOverlayBindings
86+
.Select(entry => new PreparedTerrainOverlayTextureReference(
6187
entry.TerrainMeshCode,
6288
entry.TerrainOverlay,
63-
cancellationToken))
89+
terrainTexturesByOverlay[entry.TerrainOverlay]))
6490
.ToArray();
65-
PreparedTextureReference?[] preparedTextureResults = await Task.WhenAll(
66-
terrainOverlayTexturePreparationTasks
67-
.Concat(cityObject.Materials
68-
.Where(static material => material.TexturePayload is not null)
69-
.Select(PrepareDirectMaterialTextureReferenceAsync)
70-
.ToArray()));
71-
return preparedTextureResults
91+
return preparedTerrainTextureReferences
92+
.Cast<PreparedTextureReference>()
93+
.Concat(preparedDirectTextureResults.OfType<PreparedTextureReference>())
7294
.OfType<PreparedTextureReference>()
7395
.ToArray();
7496
}
7597

76-
private async Task<PreparedTextureReference?> PrepareTerrainOverlayTextureReferenceAsync(
98+
private async Task<GeneratedTerrainTexture> PrepareTerrainOverlayTextureAsync(
7799
LiveSendRunState state,
78100
IResoniteLinkClient routedClient,
79101
Action<string>? progressReporter,
80-
ThirdRegionalMeshCode terrainMeshCode,
81102
TerrainTextureOverlay terrainTextureOverlay,
82103
CancellationToken cancellationToken)
83104
{
@@ -107,10 +128,7 @@ public async Task<PreparedTextureReference[]> PrepareAsync(
107128
}
108129
}
109130

110-
return new PreparedTerrainOverlayTextureReference(
111-
terrainMeshCode,
112-
terrainTextureOverlay,
113-
terrainTexture);
131+
return terrainTexture;
114132
}
115133

116134
private static TerrainTextureSource[] GetTrackedTerrainTextureSources(

tests/PlateauResoniteLink.Tests/Profiles/CityGmlSurfaceMaterialResolverTests.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void EnumerateSurfacesPreservesLazyScanAfterFirstGeneratedDemSurface()
4040
}
4141

4242
[Fact]
43-
public void CreateMaterialBindingUsesTerrainOverlayMeshCodeWithoutMatchingActualMeshCode()
43+
public void CreateMaterialBindingKeepsMaterialMeshCodeSeparateFromTerrainOverlayMeshCode()
4444
{
4545
ResolvedSurfaceMaterial representativeSurface = new(
4646
CreateSurface(),
@@ -60,10 +60,36 @@ public void CreateMaterialBindingUsesTerrainOverlayMeshCodeWithoutMatchingActual
6060
representativeSurface,
6161
materialIndex: 0);
6262

63-
Assert.Equal("53394525", binding.TerrainMeshCode);
63+
Assert.Equal("53394600", binding.TerrainMeshCode);
6464
Assert.Same(representativeSurface.Material.TerrainOverlay, binding.TerrainOverlay);
6565
}
6666

67+
[Fact]
68+
public void CreateMaterialBindingFallsBackToOverlayMeshCodeWhenMaterialMeshCodeIsNotThirdLevel()
69+
{
70+
TerrainTextureOverlay overlay = CreateOverlay("53394525");
71+
ResolvedSurfaceMaterial representativeSurface = new(
72+
CreateSurface(),
73+
new ResolvedMaterial(
74+
MaterialType.Standard,
75+
TexturePayload: null,
76+
TextureSourceKind.Bundled,
77+
MaterialProjection.Uv,
78+
Family: "terrain",
79+
TextureScale: null,
80+
MaterialReuseScope.Shared,
81+
TerrainOverlay: overlay),
82+
depthOffset: null);
83+
84+
MaterialBinding binding = CityGmlSurfaceMaterialResolver.CreateMaterialBinding(
85+
"533945",
86+
representativeSurface,
87+
materialIndex: 0);
88+
89+
Assert.Equal("53394525", binding.TerrainMeshCode);
90+
Assert.Same(overlay, binding.TerrainOverlay);
91+
}
92+
6793
[Fact]
6894
public void EnumerateSurfacesUsesConstructionRoleInsteadOfParsedSemanticForGeneratedNoWallSlabParts()
6995
{

tests/PlateauResoniteLink.Tests/Profiles/ImportedDynamicMaterialUvNormalizerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,6 @@ private static MaterialBinding CreateDynamicUvMaterial(
279279
TextureOffset: textureOffset,
280280
TerrainOverlayMaterial: terrainOverlay is null
281281
? null
282-
: new TerrainOverlayMaterialBinding(terrainOverlay));
282+
: new TerrainOverlayMaterialBinding(terrainOverlay.MeshCode, terrainOverlay));
283283
}
284284
}

tests/PlateauResoniteLink.Tests/Profiles/LocalCityGmlObjectProjectionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,7 +1501,7 @@ public void ProjectCityObjectUsesPerObjectAlbedoOnlyMaterialForTexturelessDemTer
15011501
}
15021502

15031503
[Fact]
1504-
public void ProjectCityObjectUsesProvidedTerrainOverlayMeshCodeForTexturelessRoof()
1504+
public void ProjectCityObjectKeepsMaterialMeshCodeForMismatchedTerrainOverlayRoof()
15051505
{
15061506
CoordinateReferenceSystem referenceSystem = CoordinateReferenceSystem.Parse("http://www.opengis.net/def/crs/EPSG/0/6697");
15071507
TerrainTextureOverlay mismatchedOverlay = CreateThirdMeshOverlay("53394526");
@@ -1523,7 +1523,7 @@ public void ProjectCityObjectUsesProvidedTerrainOverlayMeshCodeForTexturelessRoo
15231523

15241524
MaterialBinding material = Assert.Single(projected.Materials);
15251525
Assert.Same(mismatchedOverlay, material.TerrainOverlay);
1526-
Assert.Equal("53394526", material.TerrainMeshCode);
1526+
Assert.Equal("53394525", material.TerrainMeshCode);
15271527
Assert.Equal(TextureSourceKind.Dataset, material.TextureSourceKind);
15281528
}
15291529

tests/PlateauResoniteLink.Tests/Targets/NonDemCityObjectBakerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,7 @@ public async Task TryBufferAsyncPreservesTerrainOverlayAlbedoOnlyProviderWithGen
844844
DepthOffset: null,
845845
SubmeshIndices: [0],
846846
AssetScope: ResoniteMaterialAssetScope.Common,
847-
TerrainOverlayMaterial: new TerrainOverlayMaterialBinding(overlay),
847+
TerrainOverlayMaterial: new TerrainOverlayMaterialBinding(overlay.MeshCode, overlay),
848848
CommonMaterial: CommonMaterialCatalog.Create().Generic.Uv),
849849
],
850850
};

tests/PlateauResoniteLink.Tests/Targets/ResoniteDynamicMaterialUvNormalizerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,6 @@ private static ResoniteMaterialBinding CreateDynamicUvMaterial(
298298
AssetScope: ResoniteMaterialAssetScope.PresentationSlotScoped,
299299
TerrainOverlayMaterial: terrainOverlay is null
300300
? null
301-
: new TerrainOverlayMaterialBinding(terrainOverlay));
301+
: new TerrainOverlayMaterialBinding(terrainOverlay.MeshCode, terrainOverlay));
302302
}
303303
}

0 commit comments

Comments
 (0)