Skip to content

Commit 802ab63

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

20 files changed

Lines changed: 371 additions & 99 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/LiveSendExecutionState.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66

7+
using PlateauResoniteLink.Domain.Importing;
8+
79
namespace PlateauResoniteLink.Targets.Resonite;
810

911
internal sealed class LiveSendProgressSink
@@ -51,9 +53,13 @@ internal sealed class CommonMaterialAssetCache
5153

5254
internal sealed class TerrainTextureAssetCache
5355
{
54-
public AsyncInFlightResultCache<string, SharedTerrainTextureAsset> AssetsByMeshCode { get; } = new();
56+
public AsyncInFlightResultCache<TerrainTextureAssetKey, SharedTerrainTextureAsset> AssetsByOverlay { get; } = new();
5557
}
5658

59+
internal sealed record TerrainTextureAssetKey(
60+
ThirdRegionalMeshCode MeshCode,
61+
TerrainTextureOverlay Overlay);
62+
5763
internal sealed record SharedTerrainTextureAsset(
5864
Uri TextureUri,
5965
CreatedComponent TextureComponent,

src/PlateauResoniteLink/Targets/Resonite/ResonitePreparedCityObjectImporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public async Task ImportAsync(
8585
cityObject,
8686
uploadedTextureAssets.TextureUrisByPayload,
8787
uploadedTextureAssets.TerrainTextureUrisByOverlay,
88-
uploadedTextureAssets.TerrainTexturePropertyBlockComponentsByMeshCode,
88+
uploadedTextureAssets.TerrainTexturePropertyBlockComponentsByKey,
8989
message => ReportImportStep(progressReporter, cityObject, message),
9090
importStepCancellation.Token);
9191
plannedMaterials = await materialPlanningTask;

src/PlateauResoniteLink/Targets/Resonite/ResonitePreparedTextureUploader.cs

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Security.Cryptography;
5+
using System.Text;
46
using System.Threading;
57
using System.Threading.Tasks;
68

@@ -16,7 +18,7 @@ namespace PlateauResoniteLink.Targets.Resonite;
1618
internal sealed record ResoniteUploadedTextureAssetSet(
1719
Dictionary<ResoniteTexturePayload, Uri> TextureUrisByPayload,
1820
Dictionary<TerrainTextureOverlay, Uri> TerrainTextureUrisByOverlay,
19-
Dictionary<ThirdRegionalMeshCode, ResoniteComponentLocator> TerrainTexturePropertyBlockComponentsByMeshCode);
21+
Dictionary<TerrainTextureAssetKey, ResoniteComponentLocator> TerrainTexturePropertyBlockComponentsByKey);
2022

2123
internal static class ResonitePreparedTextureUploader
2224
{
@@ -28,7 +30,7 @@ public static async Task<ResoniteUploadedTextureAssetSet> UploadAsync(
2830
{
2931
Dictionary<ResoniteTexturePayload, Uri> textureUrisByPayload = new(ResoniteTexturePayloadReferenceComparer.Instance);
3032
Dictionary<TerrainTextureOverlay, Uri> terrainTextureUrisByOverlay = [];
31-
Dictionary<ThirdRegionalMeshCode, ResoniteComponentLocator> terrainTexturePropertyBlockComponentsByMeshCode = [];
33+
Dictionary<TerrainTextureAssetKey, ResoniteComponentLocator> terrainTexturePropertyBlockComponentsByKey = [];
3234
HashSet<ResoniteTexturePayload> queuedPayloads = new(ResoniteTexturePayloadReferenceComparer.Instance);
3335
List<(PreparedMaterialTextureReference Texture, Task<Uri> ImportTask)> textureImportTasks = [];
3436
List<(PreparedTerrainOverlayTextureReference Texture, Task<SharedTerrainTextureAsset> ImportTask)> terrainTextureImportTasks = [];
@@ -56,6 +58,7 @@ public static async Task<ResoniteUploadedTextureAssetSet> UploadAsync(
5658
state,
5759
importClient,
5860
terrainTexture.MeshCode,
61+
terrainTexture.Overlay,
5962
terrainTexture.TextureSource,
6063
cancellationToken)));
6164
continue;
@@ -80,33 +83,36 @@ public static async Task<ResoniteUploadedTextureAssetSet> UploadAsync(
8083
foreach ((PreparedTerrainOverlayTextureReference texture, Task<SharedTerrainTextureAsset> importTask) in terrainTextureImportTasks)
8184
{
8285
SharedTerrainTextureAsset sharedTexture = await importTask;
83-
terrainTextureUrisByOverlay.Add(texture.Overlay, sharedTexture.TextureUri);
84-
terrainTexturePropertyBlockComponentsByMeshCode.TryAdd(texture.MeshCode, sharedTexture.MainTexturePropertyBlockComponent.Locator);
86+
TerrainTextureAssetKey key = new(texture.MeshCode, texture.Overlay);
87+
terrainTextureUrisByOverlay.TryAdd(texture.Overlay, sharedTexture.TextureUri);
88+
terrainTexturePropertyBlockComponentsByKey.TryAdd(key, sharedTexture.MainTexturePropertyBlockComponent.Locator);
8589
}
8690

8791
return new ResoniteUploadedTextureAssetSet(
8892
textureUrisByPayload,
8993
terrainTextureUrisByOverlay,
90-
terrainTexturePropertyBlockComponentsByMeshCode);
94+
terrainTexturePropertyBlockComponentsByKey);
9195
}
9296

9397
private static Task<SharedTerrainTextureAsset> EnsureSharedTerrainTextureAssetAsync(
9498
LiveSendRunState state,
9599
IResoniteLinkClient importClient,
96100
ThirdRegionalMeshCode meshCode,
101+
TerrainTextureOverlay overlay,
97102
ITextureImportSource textureSource,
98103
CancellationToken cancellationToken)
99104
{
100-
return state.TerrainTextures.AssetsByMeshCode.GetOrCreateAsync(
101-
meshCode.Value,
102-
() => EnsureSharedTerrainTextureAssetCoreAsync(state, importClient, meshCode, textureSource, cancellationToken),
105+
TerrainTextureAssetKey key = new(meshCode, overlay);
106+
return state.TerrainTextures.AssetsByOverlay.GetOrCreateAsync(
107+
key,
108+
() => EnsureSharedTerrainTextureAssetCoreAsync(state, importClient, key, textureSource, cancellationToken),
103109
cancellationToken);
104110
}
105111

106112
private static async Task<SharedTerrainTextureAsset> EnsureSharedTerrainTextureAssetCoreAsync(
107113
LiveSendRunState state,
108114
IResoniteLinkClient importClient,
109-
ThirdRegionalMeshCode meshCode,
115+
TerrainTextureAssetKey key,
110116
ITextureImportSource textureSource,
111117
CancellationToken cancellationToken)
112118
{
@@ -118,11 +124,16 @@ private static async Task<SharedTerrainTextureAsset> EnsureSharedTerrainTextureA
118124
CreatedSlot meshSlot = await state.Placement.GetOrCreateSharedChildSlotAsync(
119125
importClient,
120126
terrainTexturesRoot.Locator,
121-
meshCode.Value,
127+
key.MeshCode.Value,
128+
cancellationToken);
129+
CreatedSlot overlaySlot = await state.Placement.GetOrCreateSharedChildSlotAsync(
130+
importClient,
131+
meshSlot.Locator,
132+
CreateOverlaySlotName(key),
122133
cancellationToken);
123134
SharedTerrainTextureAsset? existingTexture = await TryFindSharedTerrainTextureAssetAsync(
124135
importClient,
125-
meshSlot,
136+
overlaySlot,
126137
cancellationToken);
127138
if (existingTexture is not null)
128139
{
@@ -145,15 +156,15 @@ await importClient.UpdateComponentAsync(
145156
Uri importedTextureUri = await importClient.ImportTextureAsync(textureSource, cancellationToken);
146157
CreatedComponent textureComponent = await ResoniteMaterialPlanning.CreateComponentAsync(
147158
importClient,
148-
meshSlot.Locator,
159+
overlaySlot.Locator,
149160
"[FrooxEngine]FrooxEngine.StaticTexture2D",
150161
ResoniteSceneMaterialConventions.CreateTextureMembers(
151162
importedTextureUri,
152163
ResoniteSceneMaterialConventions.TextureMemberRole.TerrainMainTextureOverride),
153164
cancellationToken);
154165
CreatedComponent propertyBlockComponent = await CreateTerrainMainTexturePropertyBlockAsync(
155166
importClient,
156-
meshSlot.Locator,
167+
overlaySlot.Locator,
157168
textureComponent.Locator,
158169
cancellationToken);
159170
return new SharedTerrainTextureAsset(
@@ -162,13 +173,26 @@ await importClient.UpdateComponentAsync(
162173
propertyBlockComponent);
163174
}
164175

176+
private static string CreateOverlaySlotName(TerrainTextureAssetKey key)
177+
{
178+
string identity = string.Join(
179+
"|",
180+
key.MeshCode.Value,
181+
key.Overlay.PackageName,
182+
TerrainTextureDescriptorFormatting.FormatBounds(key.Overlay.GeographicBounds),
183+
key.Overlay.MaxTextureSize.ToString(System.Globalization.CultureInfo.InvariantCulture),
184+
key.Overlay.SourceDescriptorKey);
185+
string digest = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(identity))).ToLowerInvariant()[..16];
186+
return $"overlay-{digest}";
187+
}
188+
165189
private static async Task<SharedTerrainTextureAsset?> TryFindSharedTerrainTextureAssetAsync(
166190
IResoniteLinkClient importClient,
167-
CreatedSlot meshSlot,
191+
CreatedSlot overlaySlot,
168192
CancellationToken cancellationToken)
169193
{
170194
Slot? slot = await importClient.GetSlotAsync(
171-
new ResoniteTransportSlotLocator(meshSlot.Locator.Value),
195+
new ResoniteTransportSlotLocator(overlaySlot.Locator.Value),
172196
depth: 0,
173197
cancellationToken);
174198
Component? textureComponent = slot?.Components?
@@ -186,13 +210,14 @@ await importClient.UpdateComponentAsync(
186210

187211
CreatedComponent createdTextureComponent = new(new ResoniteComponentLocator(textureComponent.ID), textureComponent.ComponentType);
188212
Component? propertyBlockComponent = slot?.Components?
189-
.Where(component => IsSharedTerrainMainTexturePropertyBlockComponent(component, textureComponent.ID))
190-
.OrderBy(static component => component.ID, StringComparer.Ordinal)
213+
.Where(IsSharedTerrainMainTexturePropertyBlockComponent)
214+
.OrderByDescending(component => ReferencesComponent(component, textureComponent.ID))
215+
.ThenBy(static component => component.ID, StringComparer.Ordinal)
191216
.FirstOrDefault();
192217
CreatedComponent createdPropertyBlockComponent = propertyBlockComponent?.ID is null
193218
? await CreateTerrainMainTexturePropertyBlockAsync(
194219
importClient,
195-
meshSlot.Locator,
220+
overlaySlot.Locator,
196221
createdTextureComponent.Locator,
197222
cancellationToken)
198223
: new CreatedComponent(new ResoniteComponentLocator(propertyBlockComponent.ID), propertyBlockComponent.ComponentType);
@@ -223,15 +248,21 @@ private static Task<CreatedComponent> CreateTerrainMainTexturePropertyBlockAsync
223248
cancellationToken);
224249
}
225250

226-
private static bool IsSharedTerrainMainTexturePropertyBlockComponent(Component component, string textureComponentId)
251+
private static bool IsSharedTerrainMainTexturePropertyBlockComponent(Component component)
227252
{
228253
return string.Equals(
229254
component.ComponentType,
230255
"[FrooxEngine]FrooxEngine.MainTexturePropertyBlock",
231256
StringComparison.Ordinal)
232257
&& component.Members.TryGetValue("Texture", out Member? textureMember)
258+
&& textureMember is Reference;
259+
}
260+
261+
private static bool ReferencesComponent(Component component, string componentId)
262+
{
263+
return component.Members.TryGetValue("Texture", out Member? textureMember)
233264
&& textureMember is Reference { TargetID: string targetId }
234-
&& string.Equals(targetId, textureComponentId, StringComparison.Ordinal);
265+
&& string.Equals(targetId, componentId, StringComparison.Ordinal);
235266
}
236267

237268
private static bool IsSharedTerrainTextureComponent(Component component)

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(

0 commit comments

Comments
 (0)