Skip to content

Commit cee47fc

Browse files
committed
Refactored the way used and required extensions are calculated. Now extension textures do not generate fallback references
1 parent 092dc12 commit cee47fc

File tree

9 files changed

+167
-65
lines changed

9 files changed

+167
-65
lines changed

src/SharpGLTF.Core/Schema2/Serialization.WriteSettings.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,13 @@ public void SaveGLTF(string filePath, WriteSettings settings = null)
223223
context.WriteTextSchema2(name, this);
224224
}
225225

226-
[Obsolete("Use GetJsonPreview", true)]
227-
public string GetJSON(bool indented) { return GetJsonPreview(); }
228-
229226
/// <summary>
230227
/// Gets the JSON document of this <see cref="MODEL"/>.
231228
/// </summary>
232229
/// <returns>A JSON content.</returns>
233230
/// <remarks>
234-
/// ⚠ Beware: this method serializes the current model into a json, without taking care of the binary buffers,
231+
/// ⚠ Beware: the generated json is intended to be used for debugging purposes only;
232+
/// this method serializes the current model into a json, without taking care of the binary buffers and images,<br/>
235233
/// so the produced json might not be usable!
236234
/// </remarks>
237235
public string GetJsonPreview()

src/SharpGLTF.Core/Schema2/gltf.ExtensionsFactory.cs

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -259,75 +259,96 @@ partial class ModelRoot
259259

260260
internal void UpdateExtensionsSupport()
261261
{
262-
var used = GatherUsedExtensions();
262+
var used = GatherUsedAndRequiredExtensions();
263263

264264
// update the used list
265265
this._extensionsUsed.Clear();
266-
this._extensionsUsed.AddRange(used);
266+
this._extensionsUsed.AddRange(used.Select(item => item.ext));
267267

268-
_SetExtensionUsage("KHR_mesh_quantization", this._extensionsUsed.Contains("KHR_mesh_quantization"), true);
268+
this._extensionsRequired.Clear();
269+
this._extensionsRequired.AddRange(used.Where(item => item.isRequired == true).Select(item => item.ext));
269270
}
270271

271-
internal IEnumerable<string> GatherUsedExtensions()
272+
273+
274+
internal IEnumerable<(string ext, bool isRequired)> GatherUsedAndRequiredExtensions()
272275
{
273276
// retrieve ALL the property based objects of the whole model.
274277
var allObjects = new[] { this }
275278
.Concat(GetLogicalChildrenFlattened())
276279
.ToList();
277280

278281
// check all the extensions used by each object
279-
var used = new HashSet<string>();
280-
281-
// search for known extensions
282-
foreach (var c in allObjects)
283-
{
284-
var ids = c.Extensions
285-
.Select(item => ExtensionsFactory.Identify(c.GetType(), item.GetType()))
286-
.Where(item => !string.IsNullOrWhiteSpace(item));
287-
288-
used.UnionWith(ids);
289-
}
282+
var exts = new Dictionary<string, bool>();
290283

291284
// search for unknown extensions
292285
foreach (var unk in allObjects.SelectMany(item => item.Extensions).OfType<UnknownNode>())
293286
{
294-
used.Add(unk.Name);
287+
exts[unk.Name] = false;
295288
}
296289

297-
// search for special cases
290+
// search for known extensions
291+
foreach (var c in allObjects)
292+
{
293+
foreach(var ext in c.Extensions)
294+
{
295+
var id = ExtensionsFactory.Identify(c.GetType(), ext.GetType());
296+
if (string.IsNullOrWhiteSpace(id)) continue;
298297

299-
var isQuantized = MeshPrimitive.CheckAttributesQuantizationRequired(this);
300-
if (isQuantized) used.Add("KHR_mesh_quantization");
298+
bool isRequired = false;
301299

302-
return used;
303-
}
300+
if (ext is IExtensionTypeInfo extInfo)
301+
{
302+
isRequired = extInfo.CheckIsRequiredExtension(c);
303+
}
304304

305-
private void _SetExtensionUsage(string extension, bool used, bool required)
306-
{
307-
if (!used)
308-
{
309-
this._extensionsUsed.Remove(extension);
310-
this._extensionsRequired.Remove(extension);
311-
return;
312-
}
305+
if (exts.TryGetValue(id, out var stored)) { isRequired |= stored; }
313306

314-
if (!this._extensionsUsed.Contains(extension)) this._extensionsUsed.Add(extension);
315-
if (required && !this._extensionsRequired.Contains(extension)) this._extensionsRequired.Add(extension);
316-
}
307+
exts[id] = isRequired;
308+
}
309+
}
317310

311+
// search for special cases
312+
var isQuantized = MeshPrimitive.CheckAttributesQuantizationRequired(this);
313+
if (isQuantized) exts["KHR_mesh_quantization"] = true;
314+
315+
return exts.Select(kvp => (kvp.Key, kvp.Value));
316+
}
317+
318318
internal void _ValidateExtensions(Validation.ValidationContext validate)
319319
{
320320
foreach (var iex in this.IncompatibleExtensions)
321321
{
322322
validate._LinkThrow("Extensions", iex);
323323
}
324324

325-
foreach (var ext in GatherUsedExtensions())
325+
foreach (var ext in GatherUsedAndRequiredExtensions().Select(item => item.ext))
326326
{
327327
if (!this._extensionsUsed.Contains(ext)) validate._LinkThrow("Extensions", ext);
328328
}
329+
330+
foreach (var ext in GatherUsedAndRequiredExtensions().Where(item => item.isRequired).Select(item => item.ext))
331+
{
332+
if (!this._extensionsRequired.Contains(ext)) validate._LinkThrow("Extensions", ext);
333+
}
329334
}
330335

331336
#endregion
332337
}
338+
339+
/// <summary>
340+
/// Implemented by extensions
341+
/// </summary>
342+
public interface IExtensionTypeInfo
343+
{
344+
/// <summary>
345+
/// Checks whether the extension instance implementing this interface should be tagged as required.
346+
/// </summary>
347+
/// <param name="extensionOwner">the owner of the extension</param>
348+
/// <returns>true if the extension should be tagged as required</returns>
349+
/// <remarks>
350+
/// This is called just before saving the model.
351+
/// </remarks>
352+
public bool CheckIsRequiredExtension(ExtraProperties extensionOwner);
353+
}
333354
}

src/SharpGLTF.Core/Schema2/gltf.Material.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,41 @@ public float Dispersion
137137
/// <returns>A <see cref="MaterialChannel"/> structure. or null if it does not exist</returns>
138138
[System.Diagnostics.DebuggerStepThrough]
139139
public MaterialChannel? FindChannel(string channelKey)
140+
{
141+
return TryGetChannel(channelKey, out var channel) ? channel : null;
142+
}
143+
144+
/// <summary>
145+
/// Finds an instance of <see cref="MaterialChannel"/>
146+
/// </summary>
147+
/// <param name="channelKey">
148+
/// The channel key. Currently, these values are used:
149+
/// - "Normal"
150+
/// - "Occlusion"
151+
/// - "Emissive"
152+
/// - When material is <see cref="MaterialPBRMetallicRoughness"/>:
153+
/// - "BaseColor"
154+
/// - "MetallicRoughness"
155+
/// - When material is <see cref="MaterialPBRSpecularGlossiness"/>:
156+
/// - "Diffuse"
157+
/// - "SpecularGlossiness"
158+
/// </param>
159+
/// <returns>true if found</returns>
160+
[System.Diagnostics.DebuggerStepThrough]
161+
public bool TryGetChannel(string channelKey, out MaterialChannel channel)
140162
{
141163
foreach (var ch in Channels)
142164
{
143-
if (ch.Key.Equals(channelKey, StringComparison.OrdinalIgnoreCase)) return ch;
165+
if (ch.Key.Equals(channelKey, StringComparison.OrdinalIgnoreCase))
166+
{
167+
channel = ch;
168+
return true;
169+
}
144170
}
145171

146-
return null;
147-
}
172+
channel = default;
173+
return false;
174+
}
148175

149176
#endregion
150177

src/SharpGLTF.Core/Schema2/gltf.Textures.cs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,34 +67,41 @@ public void SetImage(Image primaryImage)
6767
Guard.NotNull(primaryImage, nameof(primaryImage));
6868
Guard.MustShareLogicalParent(this, primaryImage, nameof(primaryImage));
6969

70+
ClearImages();
71+
7072
if (primaryImage.Content.IsExtendedFormat)
71-
{
72-
var fallback = LogicalParent.UseImage(Memory.MemoryImage.DefaultPngImage);
73-
SetImages(primaryImage, fallback);
73+
{
74+
_SetExtendedImage(primaryImage);
7475
}
7576
else
76-
{
77-
ClearImages();
77+
{
7878
_source = primaryImage.LogicalIndex;
79-
}
79+
}
8080
}
8181

8282
public void SetImages(Image primaryImage, Image fallbackImage)
8383
{
84-
Guard.NotNull(primaryImage, nameof(primaryImage));
85-
Guard.NotNull(fallbackImage, nameof(fallbackImage));
86-
Guard.MustShareLogicalParent(this, primaryImage, nameof(primaryImage));
87-
Guard.MustShareLogicalParent(this, fallbackImage, nameof(fallbackImage));
88-
Guard.IsTrue(primaryImage.Content.IsExtendedFormat, "Primary image must be DDS, WEBP or KTX2");
84+
Guard.NotNull(fallbackImage, nameof(fallbackImage));
85+
Guard.MustShareLogicalParent(this, fallbackImage, nameof(fallbackImage));
8986
Guard.IsTrue(fallbackImage.Content.IsJpg || fallbackImage.Content.IsPng, nameof(fallbackImage), "Fallback image must be PNG or JPEG");
9087

91-
if (primaryImage.Content.IsDds) { _UseDDSTexture().Image = primaryImage; }
92-
if (primaryImage.Content.IsWebp) { _UseWEBPTexture().Image = primaryImage; }
93-
if (primaryImage.Content.IsKtx2) { _UseKTX2Texture().Image = primaryImage; }
94-
88+
_SetExtendedImage(primaryImage);
9589
_source = fallbackImage.LogicalIndex;
9690
}
9791

92+
private void _SetExtendedImage(Image extendedImage)
93+
{
94+
Guard.NotNull(extendedImage, nameof(extendedImage));
95+
Guard.MustShareLogicalParent(this, extendedImage, nameof(extendedImage));
96+
Guard.IsTrue(extendedImage.Content.IsExtendedFormat, "Primary image must be DDS, WEBP or KTX2");
97+
98+
if (extendedImage.Content.IsDds) { _UseDDSTexture().Image = extendedImage; return; }
99+
if (extendedImage.Content.IsWebp) { _UseWEBPTexture().Image = extendedImage; return; }
100+
if (extendedImage.Content.IsKtx2) { _UseKTX2Texture().Image = extendedImage; return; }
101+
102+
throw new NotImplementedException("Unknown image format");
103+
}
104+
98105
public void ClearImages()
99106
{
100107
_source = null;
@@ -149,7 +156,7 @@ protected override void OnValidateReferences(ValidationContext validate)
149156
#endregion
150157
}
151158

152-
partial class TextureDDS
159+
partial class TextureDDS : IExtensionTypeInfo
153160
{
154161
internal TextureDDS(Texture parent)
155162
{
@@ -172,9 +179,16 @@ public Image Image
172179
_source = value?.LogicalIndex;
173180
}
174181
}
182+
183+
public bool CheckIsRequiredExtension(ExtraProperties extensionOwner)
184+
{
185+
if (extensionOwner is not Texture tex) return false;
186+
187+
return tex.FallbackImage == null;
188+
}
175189
}
176190

177-
partial class TextureWEBP
191+
partial class TextureWEBP : IExtensionTypeInfo
178192
{
179193
internal TextureWEBP(Texture parent)
180194
{
@@ -197,9 +211,16 @@ public Image Image
197211
_source = value?.LogicalIndex;
198212
}
199213
}
214+
215+
public bool CheckIsRequiredExtension(ExtraProperties extensionOwner)
216+
{
217+
if (extensionOwner is not Texture tex) return false;
218+
219+
return tex.FallbackImage == null;
220+
}
200221
}
201222

202-
partial class TextureKTX2
223+
partial class TextureKTX2 : IExtensionTypeInfo
203224
{
204225
internal TextureKTX2(Texture parent)
205226
{
@@ -222,6 +243,13 @@ public Image Image
222243
_source = value?.LogicalIndex;
223244
}
224245
}
246+
247+
public bool CheckIsRequiredExtension(ExtraProperties extensionOwner)
248+
{
249+
if (extensionOwner is not Texture tex) return false;
250+
251+
return tex.FallbackImage == null;
252+
}
225253
}
226254

227255
[System.Diagnostics.DebuggerDisplay("TextureSampler[{LogicalIndex}] {Name}")]
@@ -410,6 +438,9 @@ public Texture UseTexture(Image primary, Image fallback, TextureSampler sampler
410438

411439
tex.Sampler = sampler;
412440

441+
System.Diagnostics.Debug.Assert((primary != null) == (tex.PrimaryImage != null), "primary image incorrectly set");
442+
System.Diagnostics.Debug.Assert((fallback != null) == (tex.FallbackImage != null), "fallback image incorrectly set");
443+
413444
return tex;
414445
}
415446
}

src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -505,10 +505,9 @@ public static void CopyChannelsTo(this MaterialBuilder srcMaterial, Material dst
505505
var src = srcMaterial.GetChannel(k);
506506
if (src == null) continue;
507507

508-
var dst = dstMaterial.FindChannel(k.ToString());
509-
if (dst == null) continue;
508+
if (!dstMaterial.TryGetChannel(k.ToString(), out var dst)) continue;
510509

511-
src.CopyTo(dst.Value);
510+
src.CopyTo(dst);
512511
}
513512
}
514513

@@ -549,7 +548,7 @@ public static void CopyTo(this ChannelBuilder srcChannel, MaterialChannel dstCha
549548
if (srcXform != null)
550549
{
551550
dstChannel.SetTransform(srcXform.Offset, srcXform.Scale, srcXform.Rotation, srcXform.CoordinateSetOverride);
552-
}
551+
}
553552
}
554553

555554
private static Image _ConvertToImage(MaterialChannel dstChannel, ImageBuilder srcImage)

tests/SharpGLTF.Core.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ private static ModelRoot _LoadModel(string f, bool tryFix = false)
5858
// check extensions used
5959
if (unsupportedExtensions.All(uex => !model.ExtensionsUsed.Contains(uex)))
6060
{
61-
var detectedExtensions = model.GatherUsedExtensions().ToArray();
61+
var detectedExtensions = model.GatherUsedAndRequiredExtensions().Select(item => item.ext).ToArray();
6262
Assert.That(detectedExtensions, Is.EquivalentTo(model.ExtensionsUsed));
6363
}
6464
}

tests/SharpGLTF.Core.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public void LoadUniVRM()
114114
var model = ModelRoot.Load(path);
115115
Assert.That(model, Is.Not.Null);
116116

117-
var flattenExtensions = model.GatherUsedExtensions().ToArray();
117+
var flattenExtensions = model.GatherUsedAndRequiredExtensions().ToArray();
118118

119119
model.AttachToCurrentTest("AliceModel.glb");
120120
}

tests/SharpGLTF.NUnit/TestFiles.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,11 @@ public static IReadOnlyList<string> GetKhronosValidationPaths()
205205
public static IReadOnlyList<string> GetBabylonJSModelsPaths()
206206
{
207207
var skipAlways = new string[]
208-
{
208+
{
209209
"ClearCoatTest.gltf", // validator reports errors.
210210
"ClearCoatTest.glb", // validator reports errors.
211211
"\\Elf\\Elf.gltf", // validator reports invalid inverse bind matrices.
212+
"\\meshes\\Bee.glb", // validator reports errors.
212213
"\\meshes\\Tests\\AssetGenerator", // already covered separately.
213214
"\\meshes\\KHR_materials_volume_testing.glb", // draco compression-
214215
"\\meshes\\Yeti\\MayaExport\\", // validator reports out of bounds accesor

0 commit comments

Comments
 (0)