11using System ;
22using System . Collections . Generic ;
33using System . Linq ;
4+ using System . Security . Cryptography ;
5+ using System . Text ;
46using System . Threading ;
57using System . Threading . Tasks ;
68
@@ -16,7 +18,7 @@ namespace PlateauResoniteLink.Targets.Resonite;
1618internal sealed record ResoniteUploadedTextureAssetSet (
1719 Dictionary < ResoniteTexturePayload , Uri > TextureUrisByPayload ,
1820 Dictionary < TerrainTextureOverlay , Uri > TerrainTextureUrisByOverlay ,
19- Dictionary < ThirdRegionalMeshCode , ResoniteComponentLocator > TerrainTexturePropertyBlockComponentsByMeshCode ) ;
21+ Dictionary < TerrainTextureAssetKey , ResoniteComponentLocator > TerrainTexturePropertyBlockComponentsByKey ) ;
2022
2123internal 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,27 @@ 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+ ( ( int ) key . Overlay . LicenseMode ) . ToString ( System . Globalization . CultureInfo . InvariantCulture ) ,
185+ key . Overlay . SourceDescriptorKey ) ;
186+ string digest = Convert . ToHexString ( SHA256 . HashData ( Encoding . UTF8 . GetBytes ( identity ) ) ) . ToLowerInvariant ( ) [ ..16 ] ;
187+ return $ "overlay-{ digest } ";
188+ }
189+
165190 private static async Task < SharedTerrainTextureAsset ? > TryFindSharedTerrainTextureAssetAsync (
166191 IResoniteLinkClient importClient ,
167- CreatedSlot meshSlot ,
192+ CreatedSlot overlaySlot ,
168193 CancellationToken cancellationToken )
169194 {
170195 Slot ? slot = await importClient . GetSlotAsync (
171- new ResoniteTransportSlotLocator ( meshSlot . Locator . Value ) ,
196+ new ResoniteTransportSlotLocator ( overlaySlot . Locator . Value ) ,
172197 depth : 0 ,
173198 cancellationToken ) ;
174199 Component ? textureComponent = slot ? . Components ?
@@ -186,13 +211,14 @@ await importClient.UpdateComponentAsync(
186211
187212 CreatedComponent createdTextureComponent = new ( new ResoniteComponentLocator ( textureComponent . ID ) , textureComponent . ComponentType ) ;
188213 Component ? propertyBlockComponent = slot ? . Components ?
189- . Where ( component => IsSharedTerrainMainTexturePropertyBlockComponent ( component , textureComponent . ID ) )
190- . OrderBy ( static component => component . ID , StringComparer . Ordinal )
214+ . Where ( IsSharedTerrainMainTexturePropertyBlockComponent )
215+ . OrderByDescending ( component => ReferencesComponent ( component , textureComponent . ID ) )
216+ . ThenBy ( static component => component . ID , StringComparer . Ordinal )
191217 . FirstOrDefault ( ) ;
192218 CreatedComponent createdPropertyBlockComponent = propertyBlockComponent ? . ID is null
193219 ? await CreateTerrainMainTexturePropertyBlockAsync (
194220 importClient ,
195- meshSlot . Locator ,
221+ overlaySlot . Locator ,
196222 createdTextureComponent . Locator ,
197223 cancellationToken )
198224 : new CreatedComponent ( new ResoniteComponentLocator ( propertyBlockComponent . ID ) , propertyBlockComponent . ComponentType ) ;
@@ -223,15 +249,21 @@ private static Task<CreatedComponent> CreateTerrainMainTexturePropertyBlockAsync
223249 cancellationToken ) ;
224250 }
225251
226- private static bool IsSharedTerrainMainTexturePropertyBlockComponent ( Component component , string textureComponentId )
252+ private static bool IsSharedTerrainMainTexturePropertyBlockComponent ( Component component )
227253 {
228254 return string . Equals (
229255 component . ComponentType ,
230256 "[FrooxEngine]FrooxEngine.MainTexturePropertyBlock" ,
231257 StringComparison . Ordinal )
232258 && component . Members . TryGetValue ( "Texture" , out Member ? textureMember )
259+ && textureMember is Reference ;
260+ }
261+
262+ private static bool ReferencesComponent ( Component component , string componentId )
263+ {
264+ return component . Members . TryGetValue ( "Texture" , out Member ? textureMember )
233265 && textureMember is Reference { TargetID : string targetId }
234- && string . Equals ( targetId , textureComponentId , StringComparison . Ordinal ) ;
266+ && string . Equals ( targetId , componentId , StringComparison . Ordinal ) ;
235267 }
236268
237269 private static bool IsSharedTerrainTextureComponent ( Component component )
0 commit comments