Skip to content

Commit 617e678

Browse files
committed
WIP: Adding "TryFix" validation mode.
1 parent b8c39bf commit 617e678

File tree

15 files changed

+209
-60
lines changed

15 files changed

+209
-60
lines changed

examples/Example1/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ static void Main(string[] args)
4141

4242
var scene = new SharpGLTF.Scenes.SceneBuilder();
4343

44-
scene.AddMesh(mesh, Matrix4x4.Identity);
44+
scene.AddRigidMesh(mesh, Matrix4x4.Identity);
4545

4646
// save the model in different formats
4747

src/Shared/_Extensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,28 @@ internal static bool IsValidNormal(this Vector3 normal)
146146
return true;
147147
}
148148

149+
internal static Vector3 SanitizeNormal(this Vector3 normal)
150+
{
151+
var isn = normal._IsFinite() && normal.LengthSquared() > 0;
152+
return isn ? Vector3.Normalize(normal) : Vector3.UnitZ;
153+
}
154+
149155
internal static bool IsValidTangent(this Vector4 tangent)
150156
{
151157
if (tangent.W != 1 && tangent.W != -1) return false;
152158

153159
return new Vector3(tangent.X, tangent.Y, tangent.Z).IsValidNormal();
154160
}
155161

162+
internal static Vector4 SanitizeTangent(this Vector4 tangent)
163+
{
164+
var n = new Vector3(tangent.X, tangent.Y, tangent.Z);
165+
var s = float.IsNaN(tangent.W) ? 1 : tangent.W;
166+
var isn = n._IsFinite() && n.LengthSquared() > 0;
167+
n = isn ? Vector3.Normalize(n) : Vector3.UnitX;
168+
return new Vector4(n, s > 0 ? 1 : -1);
169+
}
170+
156171
internal static Matrix4x4 Inverse(this Matrix4x4 src)
157172
{
158173
if (!Matrix4x4.Invert(src, out Matrix4x4 dst)) Guard.IsTrue(false, nameof(src), "Matrix cannot be inverted.");

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,10 @@ internal void ValidateNormals(Validation.ValidationContext result)
456456

457457
for (int i = 0; i < normals.Count; ++i)
458458
{
459-
result.CheckIsUnitLength(i, normals[i]);
459+
if (result.TryFixUnitLength(i, normals[i]))
460+
{
461+
normals[i] = normals[i].SanitizeNormal();
462+
}
460463
}
461464
}
462465

@@ -473,7 +476,10 @@ internal void ValidateTangents(Validation.ValidationContext result)
473476

474477
for (int i = 0; i < tangents.Count; ++i)
475478
{
476-
result.CheckIsTangent(i, tangents[i]);
479+
if (result.TryFixTangent(i, tangents[i]))
480+
{
481+
tangents[i] = tangents[i].SanitizeTangent();
482+
}
477483
}
478484
}
479485

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,22 @@ internal void ValidateBufferUsageGPU(Validation.ValidationContext result, Buffer
266266

267267
internal void ValidateBufferUsageData(Validation.ValidationContext result)
268268
{
269-
if (this.ByteStride != 0) result.AddLinkError("BufferView", "Unexpected ByteStride found. Expected 0");
269+
if (this._byteStride.HasValue)
270+
{
271+
if (result.TryFixLink("BufferView", "Unexpected ByteStride found. Expected null"))
272+
{
273+
this._byteStride = null;
274+
}
275+
}
270276

271277
result = result.GetContext(this);
272278

273279
if (!this._target.HasValue) return;
274280

275-
result.AddLinkError("Device Buffer Target", $"is set as {this._target.Value}. But an accessor wants to use it as a plain data buffer.");
281+
if (result.TryFixLink("Device Buffer Target", $"is set as {this._target.Value}. But an accessor wants to use it as a plain data buffer."))
282+
{
283+
this._target = null;
284+
}
276285
}
277286

278287
#endregion

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ public void SetPerspectiveMode(float? aspectRatio, float yfov, float znear, floa
9191
#endregion
9292
}
9393

94+
/// <summary>
95+
/// Common interface for <see cref="CameraOrthographic"/> and <see cref="CameraPerspective"/>.
96+
/// </summary>
9497
public interface ICamera
9598
{
9699
bool IsOrthographic { get; }

src/SharpGLTF.Core/Schema2/gltf.Serialization.Read.cs

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
using System.Text;
66

77
using Newtonsoft.Json;
8+
89
using BYTES = System.ArraySegment<byte>;
910

1011
namespace SharpGLTF.Schema2
1112
{
1213
using MODEL = ModelRoot;
14+
using VALIDATIONMODE = Validation.ValidationMode;
1315

1416
/// <summary>
1517
/// Callback used for loading associated files of current model.
@@ -46,7 +48,10 @@ internal ReadSettings(string filePath)
4648
/// </summary>
4749
public AssetReader FileReader { get; set; }
4850

49-
public Boolean SkipValidation { get; set; }
51+
/// <summary>
52+
/// Gets or sets a value indicating the level of validation applied when loading a file.
53+
/// </summary>
54+
public VALIDATIONMODE Validation { get; set; } = VALIDATIONMODE.Strict;
5055
}
5156

5257
partial class ModelRoot
@@ -84,13 +89,16 @@ public static Validation.ValidationResult Validate(string filePath)
8489
/// Reads a <see cref="MODEL"/> instance from a path pointing to a GLB or a GLTF file
8590
/// </summary>
8691
/// <param name="filePath">A valid file path.</param>
92+
/// <param name="vmode">Defines the file validation level.</param>
8793
/// <returns>A <see cref="MODEL"/> instance.</returns>
88-
public static MODEL Load(string filePath)
94+
public static MODEL Load(string filePath, VALIDATIONMODE vmode = VALIDATIONMODE.Strict)
8995
{
9096
Guard.FilePathMustExist(filePath, nameof(filePath));
9197

9298
var settings = new ReadSettings(filePath);
9399

100+
settings.Validation = vmode;
101+
94102
using (var s = File.OpenRead(filePath))
95103
{
96104
return Read(s, settings);
@@ -182,14 +190,16 @@ public static MODEL ParseGLTF(String jsonContent, ReadSettings settings)
182190
return mv.Model;
183191
}
184192

185-
public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName)
193+
public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName, VALIDATIONMODE vmode = VALIDATIONMODE.Strict)
186194
{
187195
Guard.NotNull(files, nameof(files));
188196

189197
var jsonBytes = files[fileName];
190198

191199
var settings = new ReadSettings(fn => files[fn]);
192200

201+
settings.Validation = vmode;
202+
193203
using (var m = new MemoryStream(jsonBytes.Array, jsonBytes.Offset, jsonBytes.Count))
194204
{
195205
using (var tr = new StreamReader(m))
@@ -248,11 +258,11 @@ private static (MODEL Model, Validation.ValidationResult Validation) _Read(TextR
248258
Guard.NotNull(textReader, nameof(textReader));
249259
Guard.NotNull(settings, nameof(settings));
250260

261+
var root = new MODEL();
262+
var vcontext = new Validation.ValidationResult(root, settings.Validation);
263+
251264
using (var reader = new JsonTextReader(textReader))
252265
{
253-
var root = new MODEL();
254-
var vcontext = new Validation.ValidationResult(root);
255-
256266
if (!reader.Read())
257267
{
258268
vcontext.AddError(new Validation.ModelException(root, "Json is empty"));
@@ -268,36 +278,36 @@ private static (MODEL Model, Validation.ValidationResult Validation) _Read(TextR
268278
vcontext.AddError(new Validation.SchemaException(root, rex));
269279
return (null, vcontext);
270280
}
281+
}
271282

272-
// schema validation
273-
274-
root.ValidateReferences(vcontext.GetContext(root));
275-
var ex = vcontext.Errors.FirstOrDefault();
276-
if (ex != null) return (null, vcontext);
283+
// schema validation
277284

278-
// resolve external references
285+
root.ValidateReferences(vcontext.GetContext(root));
286+
var ex = vcontext.Errors.FirstOrDefault();
287+
if (ex != null) return (null, vcontext);
279288

280-
foreach (var buffer in root._buffers)
281-
{
282-
buffer._ResolveUri(settings.FileReader);
283-
}
289+
// resolve external references
284290

285-
foreach (var image in root._images)
286-
{
287-
image._ResolveUri(settings.FileReader);
288-
}
291+
foreach (var buffer in root._buffers)
292+
{
293+
buffer._ResolveUri(settings.FileReader);
294+
}
289295

290-
// full validation
296+
foreach (var image in root._images)
297+
{
298+
image._ResolveUri(settings.FileReader);
299+
}
291300

292-
if (!settings.SkipValidation)
293-
{
294-
root.Validate(vcontext.GetContext(root));
295-
ex = vcontext.Errors.FirstOrDefault();
296-
if (ex != null) return (null, vcontext);
297-
}
301+
// full validation
298302

299-
return (root, vcontext);
303+
if (settings.Validation != VALIDATIONMODE.Skip)
304+
{
305+
root.Validate(vcontext.GetContext(root));
306+
ex = vcontext.Errors.FirstOrDefault();
307+
if (ex != null) return (null, vcontext);
300308
}
309+
310+
return (root, vcontext);
301311
}
302312

303313
#endregion

src/SharpGLTF.Core/Validation/ValidationContext.cs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public ValidationContext(ValidationResult result, TARGET target)
3636

3737
public ValidationResult Result => _Result;
3838

39+
public bool TryFix => Result.Mode == Validation.ValidationMode.TryFix;
40+
3941
#endregion
4042

4143
#region API
@@ -44,12 +46,30 @@ public ValidationContext(ValidationResult result, TARGET target)
4446

4547
public void AddSchemaError(ValueLocation location, string message) { AddSchemaError(location.ToString(_Target, message)); }
4648

49+
public bool TryFixLink(ValueLocation location, string message)
50+
{
51+
if (TryFix) AddLinkWarning(location.ToString(_Target, message));
52+
else AddLinkError(location.ToString(_Target, message));
53+
54+
return TryFix;
55+
}
56+
57+
public bool TryFixData(ValueLocation location, string message)
58+
{
59+
if (TryFix) AddDataWarning(location.ToString(_Target, message));
60+
else AddDataError(location.ToString(_Target, message));
61+
62+
return TryFix;
63+
}
64+
4765
public void AddLinkError(ValueLocation location, string message) { AddLinkError(location.ToString(_Target, message)); }
4866

4967
public void AddLinkWarning(String format, params object[] args) { AddLinkWarning(String.Format(format, args)); }
5068

5169
public void AddDataError(ValueLocation location, string message) { AddDataError(location.ToString(_Target, message)); }
5270

71+
public void AddDataWarning(ValueLocation location, string message) { AddDataWarning(location.ToString(_Target, message)); }
72+
5373
public void AddSemanticWarning(String format, params object[] args) { AddSemanticWarning(String.Format(format, args)); }
5474

5575
public void AddSemanticError(String format, params object[] args) { AddSemanticError(String.Format(format, args)); }
@@ -75,6 +95,12 @@ public void AddSchemaError(string message)
7595
_Result.AddError(ex);
7696
}
7797

98+
public void AddDataWarning(string message)
99+
{
100+
var ex = new DataException(_Target, message);
101+
_Result.AddWarning(ex);
102+
}
103+
78104
public void AddDataError(string message)
79105
{
80106
var ex = new DataException(_Target, message);
@@ -221,21 +247,22 @@ public bool CheckIsFinite(ValueLocation location, System.Numerics.Quaternion? va
221247
return false;
222248
}
223249

224-
public void CheckIsUnitLength(ValueLocation location, System.Numerics.Vector3? value)
250+
public bool TryFixUnitLength(ValueLocation location, System.Numerics.Vector3? value)
225251
{
226-
if (!value.HasValue) return;
227-
if (!CheckIsFinite(location, value)) return;
228-
if (value.Value.IsValidNormal()) return;
229-
AddDataError(location, $"is not of unit length: {value.Value.Length()}.");
252+
if (!value.HasValue) return false;
253+
if (!CheckIsFinite(location, value)) return false;
254+
if (value.Value.IsValidNormal()) return false;
255+
256+
return TryFixData(location, $"is not of unit length: {value.Value.Length()}.");
230257
}
231258

232-
public void CheckIsTangent(ValueLocation location, System.Numerics.Vector4 tangent)
259+
public bool TryFixTangent(ValueLocation location, System.Numerics.Vector4 tangent)
233260
{
234-
CheckIsUnitLength(location, new System.Numerics.Vector3(tangent.X, tangent.Y, tangent.Z));
261+
if (TryFixUnitLength(location, new System.Numerics.Vector3(tangent.X, tangent.Y, tangent.Z))) return true;
235262

236-
if (tangent.W == 1 || tangent.W == -1) return;
263+
if (tangent.W == 1 || tangent.W == -1) return false;
237264

238-
AddDataError(location, $"has invalid value: {tangent.W}. Must be 1.0 or -1.0.");
265+
return TryFixData(location, $"has invalid value: {tangent.W}. Must be 1.0 or -1.0.");
239266
}
240267

241268
public void CheckIsInRange(ValueLocation location, System.Numerics.Vector4 v, float minInclusive, float maxInclusive)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace SharpGLTF.Validation
6+
{
7+
/// <summary>
8+
/// Defines validation modes for reading files.
9+
/// </summary>
10+
public enum ValidationMode
11+
{
12+
/// <summary>
13+
/// Skip validation completely.
14+
/// </summary>
15+
Skip,
16+
17+
/// <summary>
18+
/// In some specific cases, the file can be fixed, at which point the errors successfully fixed will be reported as warnings.
19+
/// </summary>
20+
TryFix,
21+
22+
/// <summary>
23+
/// Full validation, any error throws an exception.
24+
/// </summary>
25+
Strict
26+
}
27+
}

src/SharpGLTF.Core/Validation/ValidationResult.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ public sealed class ValidationResult
1111
{
1212
#region lifecycle
1313

14-
public ValidationResult(Schema2.ModelRoot root, bool instantThrow = false)
14+
public ValidationResult(Schema2.ModelRoot root, ValidationMode mode, bool instantThrow = false)
1515
{
1616
_Root = root;
17+
_Mode = mode;
1718
_InstantThrow = instantThrow;
1819
}
1920

@@ -22,6 +23,7 @@ public ValidationResult(Schema2.ModelRoot root, bool instantThrow = false)
2223
#region data
2324

2425
private readonly Schema2.ModelRoot _Root;
26+
private readonly ValidationMode _Mode;
2527
private readonly bool _InstantThrow;
2628

2729
private readonly List<Exception> _Errors = new List<Exception>();
@@ -33,6 +35,8 @@ public ValidationResult(Schema2.ModelRoot root, bool instantThrow = false)
3335

3436
public Schema2.ModelRoot Root => _Root;
3537

38+
public ValidationMode Mode => _Mode;
39+
3640
public IEnumerable<Exception> Errors => _Errors;
3741

3842
public bool HasErrors => _Errors.Count > 0;

src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public static IReadOnlyDictionary<Vector3, Vector3> CalculateSmoothNormals<TMate
8686
{
8787
var posnrm = new Dictionary<Vector3, Vector3>();
8888

89-
void addDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
89+
static void addDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
9090
{
9191
if (!dir._IsFinite()) return;
9292
if (!dict.TryGetValue(pos, out Vector3 n)) n = Vector3.Zero;

0 commit comments

Comments
 (0)