|
|
@@ -18,134 +18,138 @@ namespace SharpGLTF.Schema2
|
|
|
/// </summary>
|
|
|
/// <param name="assetName">the asset relative path.</param>
|
|
|
/// <returns>The file contents as a <see cref="byte"/> array.</returns>
|
|
|
- public delegate BYTES AssetReader(String assetName);
|
|
|
+ public delegate BYTES FileReaderCallback(String assetName);
|
|
|
+
|
|
|
+ public delegate Boolean ImageReaderCallback(Image image);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Configuration settings for reading model files.
|
|
|
/// </summary>
|
|
|
public class ReadSettings
|
|
|
{
|
|
|
- public ReadSettings()
|
|
|
- {
|
|
|
- }
|
|
|
+ #region lifecycle
|
|
|
|
|
|
- internal ReadSettings(AssetReader reader)
|
|
|
- {
|
|
|
- FileReader = reader;
|
|
|
- }
|
|
|
+ public ReadSettings() { }
|
|
|
|
|
|
- internal ReadSettings(string filePath)
|
|
|
+ public ReadSettings(ReadSettings other)
|
|
|
{
|
|
|
- Guard.FilePathMustExist(filePath, nameof(filePath));
|
|
|
-
|
|
|
- var dir = Path.GetDirectoryName(filePath);
|
|
|
-
|
|
|
- FileReader = assetFileName => new BYTES(File.ReadAllBytes(Path.Combine(dir, assetFileName)));
|
|
|
+ this.Validation = other.Validation;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Gets or sets the <see cref="AssetReader"/> delegate used to read satellite files.
|
|
|
- /// </summary>
|
|
|
- public AssetReader FileReader { get; set; }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region properties
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets or sets a value indicating the level of validation applied when loading a file.
|
|
|
/// </summary>
|
|
|
public VALIDATIONMODE Validation { get; set; } = VALIDATIONMODE.Strict;
|
|
|
+
|
|
|
+ #endregion
|
|
|
}
|
|
|
|
|
|
- partial class ModelRoot
|
|
|
+ public class ReadContext : ReadSettings
|
|
|
{
|
|
|
- #region validate
|
|
|
+ #region lifecycle
|
|
|
|
|
|
- public static Validation.ValidationResult Validate(string filePath)
|
|
|
+ public static ReadContext Create(FileReaderCallback callback)
|
|
|
+ {
|
|
|
+ Guard.NotNull(callback, nameof(callback));
|
|
|
+
|
|
|
+ return new ReadContext(callback);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static ReadContext CreateFromFile(string filePath)
|
|
|
{
|
|
|
Guard.FilePathMustExist(filePath, nameof(filePath));
|
|
|
|
|
|
- var settings = new ReadSettings(filePath);
|
|
|
+ var dir = Path.GetDirectoryName(filePath);
|
|
|
|
|
|
- using (var stream = File.OpenRead(filePath))
|
|
|
- {
|
|
|
- bool binaryFile = glb._Identify(stream);
|
|
|
+ return CreateFromDirectory(dir);
|
|
|
+ }
|
|
|
|
|
|
- if (binaryFile) return _ReadGLB(stream, settings).Validation;
|
|
|
+ public static ReadContext CreateFromDirectory(string directoryPath)
|
|
|
+ {
|
|
|
+ return new ReadContext(assetFileName => new BYTES(File.ReadAllBytes(Path.Combine(directoryPath, assetFileName))));
|
|
|
+ }
|
|
|
|
|
|
- string content = null;
|
|
|
+ public static ReadContext CreateFromDictionary(IReadOnlyDictionary<string, BYTES> dictionary)
|
|
|
+ {
|
|
|
+ return new ReadContext(fn => dictionary[fn]);
|
|
|
+ }
|
|
|
|
|
|
- using (var streamReader = new StreamReader(stream))
|
|
|
- {
|
|
|
- content = streamReader.ReadToEnd();
|
|
|
- }
|
|
|
+ private ReadContext(FileReaderCallback reader)
|
|
|
+ {
|
|
|
+ _FileReader = reader;
|
|
|
+ }
|
|
|
|
|
|
- return _ParseGLTF(content, settings).Validation;
|
|
|
- }
|
|
|
+ internal ReadContext(ReadContext other)
|
|
|
+ : base(other)
|
|
|
+ {
|
|
|
+ this._FileReader = other._FileReader;
|
|
|
+ this.ImageReader = other.ImageReader;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
- #region read / load methods
|
|
|
+ #region data
|
|
|
+
|
|
|
+ private FileReaderCallback _FileReader;
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Reads a <see cref="MODEL"/> instance from a path pointing to a GLB or a GLTF file
|
|
|
+ /// When loading GLB, this represents the internal binary data chunk.
|
|
|
/// </summary>
|
|
|
- /// <param name="filePath">A valid file path.</param>
|
|
|
- /// <param name="vmode">Defines the file validation level.</param>
|
|
|
- /// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
- public static MODEL Load(string filePath, VALIDATIONMODE vmode = VALIDATIONMODE.Strict)
|
|
|
- {
|
|
|
- Guard.FilePathMustExist(filePath, nameof(filePath));
|
|
|
+ private Byte[] _BinaryChunk;
|
|
|
|
|
|
- var settings = new ReadSettings(filePath);
|
|
|
+ #endregion
|
|
|
|
|
|
- settings.Validation = vmode;
|
|
|
+ #region callbacks
|
|
|
|
|
|
- using (var s = File.OpenRead(filePath))
|
|
|
+ public ImageReaderCallback ImageReader { get; set; }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region API
|
|
|
+
|
|
|
+ public BYTES ReadBytes(string fileName)
|
|
|
+ {
|
|
|
+ if (_BinaryChunk != null)
|
|
|
{
|
|
|
- return Read(s, settings);
|
|
|
+ if (string.IsNullOrEmpty(fileName)) return new BYTES(_BinaryChunk);
|
|
|
}
|
|
|
+
|
|
|
+ return _FileReader(fileName);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Parses a <see cref="MODEL"/> instance from a <see cref="byte"/> array representing a GLB file
|
|
|
- /// </summary>
|
|
|
- /// <param name="glb">A <see cref="byte"/> array representing a GLB file</param>
|
|
|
- /// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
- public static MODEL ParseGLB(BYTES glb)
|
|
|
+ public Stream OpenFile(string fileName)
|
|
|
{
|
|
|
- Guard.NotNull(glb, nameof(glb));
|
|
|
+ var content = _FileReader(fileName);
|
|
|
|
|
|
- using (var m = new MemoryStream(glb.Array, glb.Offset, glb.Count, false))
|
|
|
- {
|
|
|
- return ReadGLB(m, new ReadSettings());
|
|
|
- }
|
|
|
+ return new MemoryStream(content.Array, content.Offset, content.Count);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLB or a GLTF file.
|
|
|
/// </summary>
|
|
|
/// <param name="stream">A <see cref="Stream"/> to read from.</param>
|
|
|
- /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
|
|
|
/// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
- public static MODEL Read(Stream stream, ReadSettings settings)
|
|
|
+ public MODEL Read(Stream stream)
|
|
|
{
|
|
|
Guard.NotNull(stream, nameof(stream));
|
|
|
|
|
|
bool binaryFile = glb._Identify(stream);
|
|
|
|
|
|
- if (binaryFile) return ReadGLB(stream, settings);
|
|
|
- else return ReadGLTF(stream, settings);
|
|
|
+ return binaryFile ? ReadGLB(stream) : ReadGLTF(stream);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLTF file.
|
|
|
/// </summary>
|
|
|
/// <param name="stream">A <see cref="Stream"/> to read from.</param>
|
|
|
- /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
|
|
|
/// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
- public static MODEL ReadGLTF(Stream stream, ReadSettings settings)
|
|
|
+ public MODEL ReadGLTF(Stream stream)
|
|
|
{
|
|
|
Guard.NotNull(stream, nameof(stream));
|
|
|
- Guard.NotNull(settings, nameof(settings));
|
|
|
|
|
|
string content = null;
|
|
|
|
|
|
@@ -154,21 +158,19 @@ namespace SharpGLTF.Schema2
|
|
|
content = streamReader.ReadToEnd();
|
|
|
}
|
|
|
|
|
|
- return ParseGLTF(content, settings);
|
|
|
+ return ParseGLTF(content);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLB file.
|
|
|
/// </summary>
|
|
|
/// <param name="stream">A <see cref="Stream"/> to read from.</param>
|
|
|
- /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
|
|
|
/// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
- public static MODEL ReadGLB(Stream stream, ReadSettings settings)
|
|
|
+ public MODEL ReadGLB(Stream stream)
|
|
|
{
|
|
|
Guard.NotNull(stream, nameof(stream));
|
|
|
- Guard.NotNull(settings, nameof(settings));
|
|
|
|
|
|
- var mv = _ReadGLB(stream, settings);
|
|
|
+ var mv = _ReadGLB(stream);
|
|
|
|
|
|
if (mv.Validation.HasErrors) throw mv.Validation.Errors.FirstOrDefault();
|
|
|
|
|
|
@@ -179,32 +181,42 @@ namespace SharpGLTF.Schema2
|
|
|
/// Parses a <see cref="MODEL"/> instance from a <see cref="String"/> JSON content representing a GLTF file.
|
|
|
/// </summary>
|
|
|
/// <param name="jsonContent">A <see cref="String"/> JSON content representing a GLTF file.</param>
|
|
|
- /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
|
|
|
/// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
- public static MODEL ParseGLTF(String jsonContent, ReadSettings settings)
|
|
|
+ public MODEL ParseGLTF(String jsonContent)
|
|
|
{
|
|
|
- var mv = _ParseGLTF(jsonContent, settings);
|
|
|
+ var mv = _ParseGLTF(jsonContent);
|
|
|
|
|
|
if (mv.Validation.HasErrors) throw mv.Validation.Errors.FirstOrDefault();
|
|
|
|
|
|
return mv.Model;
|
|
|
}
|
|
|
|
|
|
- public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName, VALIDATIONMODE vmode = VALIDATIONMODE.Strict)
|
|
|
+ public Validation.ValidationResult Validate(string filePath)
|
|
|
{
|
|
|
- Guard.NotNull(files, nameof(files));
|
|
|
+ using (var stream = File.OpenRead(filePath))
|
|
|
+ {
|
|
|
+ bool binaryFile = glb._Identify(stream);
|
|
|
|
|
|
- var jsonBytes = files[fileName];
|
|
|
+ if (binaryFile) return _ReadGLB(stream).Validation;
|
|
|
|
|
|
- var settings = new ReadSettings(fn => files[fn]);
|
|
|
+ string content = null;
|
|
|
+
|
|
|
+ using (var streamReader = new StreamReader(stream))
|
|
|
+ {
|
|
|
+ content = streamReader.ReadToEnd();
|
|
|
+ }
|
|
|
|
|
|
- settings.Validation = vmode;
|
|
|
+ return _ParseGLTF(content).Validation;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- using (var m = new MemoryStream(jsonBytes.Array, jsonBytes.Offset, jsonBytes.Count))
|
|
|
+ internal MODEL _ReadFromDictionary(string fileName)
|
|
|
+ {
|
|
|
+ using (var s = this.OpenFile(fileName))
|
|
|
{
|
|
|
- using (var tr = new StreamReader(m))
|
|
|
+ using (var tr = new StreamReader(s))
|
|
|
{
|
|
|
- var mv = _Read(tr, settings);
|
|
|
+ var mv = this._Read(tr);
|
|
|
|
|
|
if (mv.Validation.HasErrors) throw mv.Validation.Errors.FirstOrDefault();
|
|
|
|
|
|
@@ -215,51 +227,42 @@ namespace SharpGLTF.Schema2
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
- #region reading core
|
|
|
+ #region core
|
|
|
|
|
|
- private static (MODEL Model, Validation.ValidationResult Validation) _ReadGLB(Stream stream, ReadSettings settings)
|
|
|
+ private (MODEL Model, Validation.ValidationResult Validation) _ReadGLB(Stream stream)
|
|
|
{
|
|
|
Guard.NotNull(stream, nameof(stream));
|
|
|
- Guard.NotNull(settings, nameof(settings));
|
|
|
|
|
|
var chunks = glb.ReadBinaryFile(stream);
|
|
|
|
|
|
var dom = Encoding.UTF8.GetString(chunks[glb.CHUNKJSON]);
|
|
|
|
|
|
+ var context = this;
|
|
|
+
|
|
|
if (chunks.ContainsKey(glb.CHUNKBIN))
|
|
|
{
|
|
|
- var sourceReader = settings.FileReader;
|
|
|
-
|
|
|
- settings.FileReader =
|
|
|
- key =>
|
|
|
- string.IsNullOrEmpty(key)
|
|
|
- ?
|
|
|
- new BYTES(chunks[glb.CHUNKBIN])
|
|
|
- :
|
|
|
- sourceReader.Invoke(key);
|
|
|
+ context = new ReadContext(context); // clone instance
|
|
|
+ context._BinaryChunk = chunks[glb.CHUNKBIN];
|
|
|
}
|
|
|
|
|
|
- return _ParseGLTF(dom, settings);
|
|
|
+ return context._ParseGLTF(dom);
|
|
|
}
|
|
|
|
|
|
- private static (MODEL Model, Validation.ValidationResult Validation) _ParseGLTF(String jsonContent, ReadSettings settings)
|
|
|
+ private (MODEL Model, Validation.ValidationResult Validation) _ParseGLTF(String jsonContent)
|
|
|
{
|
|
|
Guard.NotNullOrEmpty(jsonContent, nameof(jsonContent));
|
|
|
- Guard.NotNull(settings, nameof(settings));
|
|
|
-
|
|
|
using (var tr = new StringReader(jsonContent))
|
|
|
{
|
|
|
- return _Read(tr, settings);
|
|
|
+ return _Read(tr);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static (MODEL Model, Validation.ValidationResult Validation) _Read(TextReader textReader, ReadSettings settings)
|
|
|
+ private (MODEL Model, Validation.ValidationResult Validation) _Read(TextReader textReader)
|
|
|
{
|
|
|
Guard.NotNull(textReader, nameof(textReader));
|
|
|
- Guard.NotNull(settings, nameof(settings));
|
|
|
|
|
|
var root = new MODEL();
|
|
|
- var vcontext = new Validation.ValidationResult(root, settings.Validation);
|
|
|
+ var vcontext = new Validation.ValidationResult(root, this.Validation);
|
|
|
|
|
|
using (var reader = new JsonTextReader(textReader))
|
|
|
{
|
|
|
@@ -282,32 +285,110 @@ namespace SharpGLTF.Schema2
|
|
|
|
|
|
// schema validation
|
|
|
|
|
|
- root.ValidateReferences(vcontext.GetContext(root));
|
|
|
+ root.ValidateReferences(vcontext.GetContext());
|
|
|
var ex = vcontext.Errors.FirstOrDefault();
|
|
|
if (ex != null) return (null, vcontext);
|
|
|
|
|
|
- // resolve external references
|
|
|
+ // resolve external dependencies
|
|
|
+
|
|
|
+ root._ResolveSatelliteDependencies(this);
|
|
|
|
|
|
- foreach (var buffer in root._buffers)
|
|
|
+ // full validation
|
|
|
+
|
|
|
+ if (this.Validation != VALIDATIONMODE.Skip)
|
|
|
{
|
|
|
- buffer._ResolveUri(settings.FileReader);
|
|
|
+ root.Validate(vcontext.GetContext());
|
|
|
+ ex = vcontext.Errors.FirstOrDefault();
|
|
|
+ if (ex != null) return (null, vcontext);
|
|
|
}
|
|
|
|
|
|
- foreach (var image in root._images)
|
|
|
+ return (root, vcontext);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+ }
|
|
|
+
|
|
|
+ partial class ModelRoot
|
|
|
+ {
|
|
|
+ #region validate
|
|
|
+
|
|
|
+ public static Validation.ValidationResult Validate(string filePath)
|
|
|
+ {
|
|
|
+ Guard.FilePathMustExist(filePath, nameof(filePath));
|
|
|
+
|
|
|
+ var context = ReadContext.CreateFromFile(filePath);
|
|
|
+
|
|
|
+ return context.Validate(filePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region read / load methods
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Reads a <see cref="MODEL"/> instance from a path pointing to a GLB or a GLTF file
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="filePath">A valid file path.</param>
|
|
|
+ /// <param name="vmode">Defines the file validation level.</param>
|
|
|
+ /// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
+ public static MODEL Load(string filePath, VALIDATIONMODE vmode = VALIDATIONMODE.Strict)
|
|
|
+ {
|
|
|
+ Guard.FilePathMustExist(filePath, nameof(filePath));
|
|
|
+
|
|
|
+ var context = ReadContext.CreateFromFile(filePath);
|
|
|
+
|
|
|
+ context.Validation = vmode;
|
|
|
+
|
|
|
+ using (var s = File.OpenRead(filePath))
|
|
|
{
|
|
|
- image._ResolveUri(settings.FileReader);
|
|
|
+ return context.Read(s);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // full validation
|
|
|
+ /// <summary>
|
|
|
+ /// Parses a <see cref="MODEL"/> instance from a <see cref="byte"/> array representing a GLB file
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="glb">A <see cref="byte"/> array representing a GLB file</param>
|
|
|
+ /// <returns>A <see cref="MODEL"/> instance.</returns>
|
|
|
+ public static MODEL ParseGLB(BYTES glb)
|
|
|
+ {
|
|
|
+ Guard.NotNull(glb, nameof(glb));
|
|
|
|
|
|
- if (settings.Validation != VALIDATIONMODE.Skip)
|
|
|
+ var context = ReadContext.Create(f => throw new NotSupportedException());
|
|
|
+
|
|
|
+ using (var m = new MemoryStream(glb.Array, glb.Offset, glb.Count, false))
|
|
|
{
|
|
|
- root.Validate(vcontext.GetContext(root));
|
|
|
- ex = vcontext.Errors.FirstOrDefault();
|
|
|
- if (ex != null) return (null, vcontext);
|
|
|
+ return context.ReadGLB(m);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- return (root, vcontext);
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region externals resolver
|
|
|
+
|
|
|
+ internal void _ResolveSatelliteDependencies(ReadContext context)
|
|
|
+ {
|
|
|
+ // resolve satellite buffers
|
|
|
+
|
|
|
+ foreach (var buffer in this._buffers)
|
|
|
+ {
|
|
|
+ buffer._ResolveUri(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ // resolve satellite images
|
|
|
+
|
|
|
+ foreach (var image in this._images)
|
|
|
+ {
|
|
|
+ image._ResolveUri(context);
|
|
|
+
|
|
|
+ if (context.ImageReader != null)
|
|
|
+ {
|
|
|
+ if (!context.ImageReader(image))
|
|
|
+ {
|
|
|
+ image._DiscardContent();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#endregion
|