211 lines
8.5 KiB
C#
211 lines
8.5 KiB
C#
// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
namespace GLTFast
|
|
{
|
|
|
|
static class UriHelper
|
|
{
|
|
|
|
public static Uri GetBaseUri(Uri uri)
|
|
{
|
|
if (uri == null) return null;
|
|
if (!uri.IsAbsoluteUri)
|
|
{
|
|
var directoryName = Path.GetDirectoryName(uri.OriginalString) ?? "";
|
|
return new Uri(directoryName, UriKind.Relative);
|
|
}
|
|
var uriString = uri.OriginalString;
|
|
var index = uriString.LastIndexOfAny(new[] { '/', '\\' });
|
|
return index < 0
|
|
? new Uri("", UriKind.Relative)
|
|
: new Uri(uriString.Substring(0, index + 1));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get URI that is potentially relative to another URI
|
|
/// </summary>
|
|
/// <param name="uri">Absolute or relative URI</param>
|
|
/// <param name="baseUri">Base URI</param>
|
|
/// <returns>Absolute URI that is potentially relative to baseUri</returns>
|
|
public static Uri GetUriString(string uri, Uri baseUri)
|
|
{
|
|
uri = Uri.UnescapeDataString(uri);
|
|
if (Uri.TryCreate(uri, UriKind.Absolute, out var result))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
if (baseUri != null)
|
|
{
|
|
uri = RemoveDotSegments(uri, out var parentLevels);
|
|
if (baseUri.IsAbsoluteUri)
|
|
{
|
|
for (int i = 0; i < parentLevels; i++)
|
|
{
|
|
baseUri = new Uri(baseUri, "..");
|
|
}
|
|
return new Uri(Combine(baseUri.OriginalString, uri));
|
|
}
|
|
|
|
var parentPath = baseUri.OriginalString;
|
|
for (int i = 0; i < parentLevels; i++)
|
|
{
|
|
parentPath = Path.GetDirectoryName(parentPath);
|
|
if (string.IsNullOrEmpty(parentPath))
|
|
{
|
|
baseUri = new Uri("", UriKind.Relative);
|
|
break;
|
|
}
|
|
baseUri = new Uri(parentPath, UriKind.Relative);
|
|
}
|
|
return new Uri(Path.Combine(baseUri.OriginalString, uri), UriKind.Relative);
|
|
}
|
|
return new Uri(uri, UriKind.RelativeOrAbsolute);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes relative dot segments "." and ".." and resolves them.
|
|
/// See https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
|
|
/// </summary>
|
|
/// <param name="uri">Relative input URI</param>
|
|
/// <param name="parentLevels">Number of levels going beyond this paths hierarchy (due to "..")</param>
|
|
/// <returns>Resolved/compressed input URI without dot segments</returns>
|
|
public static string RemoveDotSegments(string uri, out int parentLevels)
|
|
{
|
|
var segments = new List<string>();
|
|
var start = 0;
|
|
parentLevels = 0;
|
|
while (true)
|
|
{
|
|
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_WSA
|
|
var i = uri.IndexOfAny(new char[]{Path.DirectorySeparatorChar,Path.AltDirectorySeparatorChar},start);
|
|
#else
|
|
var i = uri.IndexOf(Path.DirectorySeparatorChar, start);
|
|
#endif
|
|
var found = i >= 0;
|
|
var len = found ? (i - start) : uri.Length - start;
|
|
if (len > 0)
|
|
{
|
|
var segment = uri.Substring(start, len);
|
|
|
|
if (segment == "..")
|
|
{
|
|
if (segments.Count > 0)
|
|
{
|
|
segments.RemoveAt(segments.Count - 1);
|
|
}
|
|
else
|
|
{
|
|
parentLevels++;
|
|
}
|
|
}
|
|
else if (segment != ".")
|
|
{
|
|
segments.Add(segment);
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
break;
|
|
}
|
|
start = i + 1;
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
var first = true;
|
|
foreach (var segment in segments)
|
|
{
|
|
if (!first)
|
|
{
|
|
sb.Append(Path.DirectorySeparatorChar);
|
|
}
|
|
sb.Append(segment);
|
|
first = false;
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detect glTF type from URI
|
|
/// </summary>
|
|
/// <param name="uri">Input URI</param>
|
|
/// <returns>True if glTF-binary, False if glTF (JSON), null if not sure.</returns>
|
|
public static bool? IsGltfBinary(Uri uri)
|
|
{
|
|
string path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString;
|
|
var index = path.LastIndexOf('.', path.Length - 1, Mathf.Min(5, path.Length));
|
|
if (index < 0) return null;
|
|
if (path.EndsWith(GltfGlobals.GlbExt, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
if (path.EndsWith(GltfGlobals.GltfExt, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detect image format from URI string
|
|
/// </summary>
|
|
/// <param name="uri">Input URI string</param>
|
|
/// <returns>ImageFormat if detected correctly, <see cref="ImageFormat.Unknown"/> otherwise</returns>
|
|
internal static ImageFormat GetImageFormatFromUri(string uri)
|
|
{
|
|
if (string.IsNullOrEmpty(uri)) return ImageFormat.Unknown;
|
|
var queryStartIndex = uri.LastIndexOf('?');
|
|
if (queryStartIndex < 0) queryStartIndex = uri.Length;
|
|
var extStartIndex = uri.LastIndexOf('.', queryStartIndex - 1, Mathf.Min(5, queryStartIndex)); // we assume that the first period before the query string is the file format period.
|
|
if (extStartIndex < 0) return ImageFormat.Unknown; // if we can't find a period, we don't know the file format.
|
|
var fileExtension = uri.Substring(extStartIndex + 1, queryStartIndex - extStartIndex - 1); // extract the file ending
|
|
if (fileExtension.Equals("png", StringComparison.OrdinalIgnoreCase)) return ImageFormat.PNG;
|
|
if (fileExtension.Equals("jpg", StringComparison.OrdinalIgnoreCase) || fileExtension.Equals("jpeg", StringComparison.OrdinalIgnoreCase)) return ImageFormat.Jpeg;
|
|
if (fileExtension.Equals("ktx", StringComparison.OrdinalIgnoreCase) || fileExtension.Equals("ktx2", StringComparison.OrdinalIgnoreCase)) return ImageFormat.Ktx;
|
|
return ImageFormat.Unknown;
|
|
}
|
|
|
|
// // string-based IsGltfBinary alternative
|
|
// // Profiling result: Faster/less memory, but for .glb/.gltf just barely better (unknown ~2x)
|
|
// // Downside: less convenient
|
|
// public static bool? IsGltfBinary( string uri ) {
|
|
// // quick glTF-binary check
|
|
// if (uri.EndsWith(GltfGlobals.glbExt, StringComparison.OrdinalIgnoreCase)) return true;
|
|
// if (uri.EndsWith(GltfGlobals.gltfExt, StringComparison.OrdinalIgnoreCase)) return false;
|
|
//
|
|
// // thorough glTF-binary extension check that strips HTTP GET parameters
|
|
// int getIndex = uri.LastIndexOf('?');
|
|
// if (getIndex >= 0) {
|
|
// var ext = uri.Substring(getIndex - GltfGlobals.gltfExt.Length, GltfGlobals.gltfExt.Length);
|
|
// if(ext.EndsWith(GltfGlobals.glbExt, StringComparison.OrdinalIgnoreCase)) return true;
|
|
// if(ext.EndsWith(GltfGlobals.gltfExt, StringComparison.OrdinalIgnoreCase)) return false;
|
|
// }
|
|
// return null;
|
|
// }
|
|
|
|
internal static string Combine(string baseUri, string uri)
|
|
{
|
|
var sepIndex = baseUri.LastIndexOfAny(new[] { '/', '\\' });
|
|
var uriStartsWithSep = uri.IndexOfAny(new[] { '/', '\\' }, 0, 1) == 0;
|
|
var trimmedUri = uriStartsWithSep
|
|
? uri.Substring(1)
|
|
: uri;
|
|
if (sepIndex > 0)
|
|
{
|
|
return sepIndex == baseUri.Length - 1
|
|
? $"{baseUri}{trimmedUri}"
|
|
: $"{baseUri}{baseUri[sepIndex]}{trimmedUri}";
|
|
}
|
|
|
|
return $"{baseUri}/{trimmedUri}";
|
|
}
|
|
}
|
|
}
|