| | 1 | | using DCL.Helpers; |
| | 2 | | using GLTF.Schema; |
| | 3 | | using System; |
| | 4 | | using System.Collections.Generic; |
| | 5 | | using System.IO; |
| | 6 | | using System.Linq; |
| | 7 | | using System.Net.Http; |
| | 8 | | using System.Security.Cryptography; |
| | 9 | | using System.Text; |
| | 10 | | using System.Text.RegularExpressions; |
| | 11 | | using UnityEditor; |
| | 12 | | using UnityEngine; |
| | 13 | | using UnityEngine.Assertions; |
| | 14 | | using UnityEngine.Networking; |
| | 15 | | using static DCL.ContentServerUtils; |
| | 16 | |
|
| | 17 | | namespace DCL.ABConverter |
| | 18 | | { |
| | 19 | | public static class MeshUtils |
| | 20 | | { |
| | 21 | | public static Bounds BuildMergedBounds(Renderer[] renderers) |
| | 22 | | { |
| 0 | 23 | | Bounds bounds = new Bounds(); |
| | 24 | |
|
| 0 | 25 | | for (int i = 0; i < renderers.Length; i++) |
| | 26 | | { |
| 0 | 27 | | if (renderers[i] == null) |
| | 28 | | continue; |
| | 29 | |
|
| 0 | 30 | | if (i == 0) |
| 0 | 31 | | bounds = renderers[i].GetSafeBounds(); |
| | 32 | | else |
| 0 | 33 | | bounds.Encapsulate(renderers[i].GetSafeBounds()); |
| | 34 | | } |
| | 35 | |
|
| 0 | 36 | | return bounds; |
| | 37 | | } |
| | 38 | |
|
| | 39 | | /// <summary> |
| | 40 | | /// This get the renderer bounds with a check to ensure the renderer is at a safe position. |
| | 41 | | /// If the renderer is too far away from 0,0,0, wasm target ensures a crash. |
| | 42 | | /// </summary> |
| | 43 | | /// <param name="renderer"></param> |
| | 44 | | /// <returns>The bounds value if the value is correct, or a mocked bounds object with clamped values if its too |
| | 45 | | public static Bounds GetSafeBounds( this Renderer renderer ) |
| | 46 | | { |
| | 47 | | // World extents are of 4800 world mts, so this limit far exceeds the world size. |
| | 48 | | const float POSITION_OVERFLOW_LIMIT = 10000; |
| | 49 | | const float POSITION_OVERFLOW_LIMIT_SQR = POSITION_OVERFLOW_LIMIT * POSITION_OVERFLOW_LIMIT; |
| | 50 | |
|
| 0 | 51 | | if ( renderer.transform.position.sqrMagnitude > POSITION_OVERFLOW_LIMIT_SQR ) |
| 0 | 52 | | return new Bounds( Vector3.one * POSITION_OVERFLOW_LIMIT, Vector3.one * 0.1f ); |
| | 53 | |
|
| 0 | 54 | | return renderer.bounds; |
| | 55 | | } |
| | 56 | | } |
| | 57 | |
|
| | 58 | | public static class PathUtils |
| | 59 | | { |
| | 60 | | /// <summary> |
| | 61 | | /// Gets the relative path ("..\..\to_file_or_dir") of another file or directory (to) in relation to the current |
| | 62 | | /// </summary> |
| | 63 | | /// <param name="to"></param> |
| | 64 | | /// <param name="from"></param> |
| | 65 | | /// <returns></returns> |
| | 66 | | public static string GetRelativePathTo(string from, string to) |
| | 67 | | { |
| | 68 | | var fromPath = Path.GetFullPath(from); |
| | 69 | | var toPath = Path.GetFullPath(to); |
| | 70 | |
|
| | 71 | | var fromUri = new Uri(fromPath); |
| | 72 | | var toUri = new Uri(toPath); |
| | 73 | |
|
| | 74 | | var relativeUri = fromUri.MakeRelativeUri(toUri); |
| | 75 | | var relativePath = Uri.UnescapeDataString(relativeUri.ToString()); |
| | 76 | |
|
| | 77 | | string result = FixDirectorySeparator(relativePath); |
| | 78 | |
|
| | 79 | | return result; |
| | 80 | | } |
| | 81 | |
|
| | 82 | | /// <summary> |
| | 83 | | /// Converts an absolute path to an Application.dataPath relative path. |
| | 84 | | /// </summary> |
| | 85 | | /// <param name="fullPath">the full path.</param> |
| | 86 | | /// <returns>the Application.dataPath relative path</returns> |
| | 87 | | public static string FullPathToAssetPath(string fullPath) |
| | 88 | | { |
| | 89 | | char ps = Path.DirectorySeparatorChar; |
| | 90 | |
|
| | 91 | | fullPath = fullPath.Replace('/', ps); |
| | 92 | | fullPath = fullPath.Replace('\\', ps); |
| | 93 | |
|
| | 94 | | string pattern = $".*?\\{ps}(?<assetpath>Assets\\{ps}.*?$)"; |
| | 95 | |
|
| | 96 | | var regex = new Regex(pattern); |
| | 97 | |
|
| | 98 | | var match = regex.Match(fullPath); |
| | 99 | |
|
| | 100 | | if (match.Success && match.Groups["assetpath"] != null) |
| | 101 | | return match.Groups["assetpath"].Value; |
| | 102 | |
|
| | 103 | | return string.Empty; |
| | 104 | | } |
| | 105 | |
|
| | 106 | | public static string FixDirectorySeparator(string path) |
| | 107 | | { |
| | 108 | | char ps = Path.DirectorySeparatorChar; |
| | 109 | | path = path.Replace('/', ps); |
| | 110 | | path = path.Replace('\\', ps); |
| | 111 | | return path; |
| | 112 | | } |
| | 113 | |
|
| | 114 | | /// <summary> |
| | 115 | | /// Convert a path relative to Application.dataPath to an absolute path. |
| | 116 | | /// </summary> |
| | 117 | | /// <param name="assetPath">The relative path</param> |
| | 118 | | /// <param name="overrideDataPath">Convert from an arbitrary path instead of Application.dataPath. Used for test |
| | 119 | | /// <returns>The full path.</returns> |
| | 120 | | public static string AssetPathToFullPath(string assetPath, string overrideDataPath = null) |
| | 121 | | { |
| | 122 | | assetPath = FixDirectorySeparator(assetPath); |
| | 123 | |
|
| | 124 | | string dataPath = overrideDataPath ?? Application.dataPath; |
| | 125 | | dataPath = FixDirectorySeparator(dataPath); |
| | 126 | |
|
| | 127 | | char ps = Path.DirectorySeparatorChar; |
| | 128 | | string dataPathWithoutAssets = dataPath.Replace($"{ps}Assets", ""); |
| | 129 | | return dataPathWithoutAssets + "/" + assetPath; |
| | 130 | | } |
| | 131 | |
|
| | 132 | | public static long GetFreeSpace() |
| | 133 | | { |
| | 134 | | DriveInfo info = new DriveInfo(new DirectoryInfo(Application.dataPath).Root.FullName); |
| | 135 | | return info.AvailableFreeSpace; |
| | 136 | | } |
| | 137 | | } |
| | 138 | |
|
| | 139 | | public static class Utils |
| | 140 | | { |
| | 141 | | internal static bool ParseOption(string[] fullCmdArgs, string optionName, int argsQty, out string[] foundArgs) { |
| | 142 | |
|
| | 143 | | internal static bool ParseOption(string optionName, int argsQty, out string[] foundArgs) { return ParseOptionExp |
| | 144 | |
|
| | 145 | | internal static bool ParseOptionExplicit(string[] rawArgsList, string optionName, int expectedArgsQty, out strin |
| | 146 | | { |
| | 147 | | foundArgs = null; |
| | 148 | |
|
| | 149 | | if (rawArgsList == null || rawArgsList.Length < expectedArgsQty + 1) |
| | 150 | | return false; |
| | 151 | |
|
| | 152 | | expectedArgsQty = Mathf.Min(expectedArgsQty, 100); |
| | 153 | |
|
| | 154 | | var foundArgsList = new List<string>(); |
| | 155 | | int argState = 0; |
| | 156 | |
|
| | 157 | | for (int i = 0; i < rawArgsList.Length; i++) |
| | 158 | | { |
| | 159 | | switch (argState) |
| | 160 | | { |
| | 161 | | case 0: |
| | 162 | | if (rawArgsList[i] == "-" + optionName) |
| | 163 | | { |
| | 164 | | argState++; |
| | 165 | | } |
| | 166 | |
|
| | 167 | | break; |
| | 168 | | default: |
| | 169 | | foundArgsList.Add(rawArgsList[i]); |
| | 170 | | argState++; |
| | 171 | | break; |
| | 172 | | } |
| | 173 | |
|
| | 174 | | if (argState > 0 && foundArgsList.Count == expectedArgsQty) |
| | 175 | | break; |
| | 176 | | } |
| | 177 | |
|
| | 178 | | if (argState == 0 || foundArgsList.Count < expectedArgsQty) |
| | 179 | | return false; |
| | 180 | |
|
| | 181 | | if (expectedArgsQty > 0) |
| | 182 | | foundArgs = foundArgsList.ToArray(); |
| | 183 | |
|
| | 184 | | return true; |
| | 185 | | } |
| | 186 | |
|
| | 187 | | internal static void Exit(int errorCode = 0) |
| | 188 | | { |
| | 189 | | Debug.Log($"Process finished with code {errorCode}"); |
| | 190 | |
|
| | 191 | | if (Application.isBatchMode) |
| | 192 | | EditorApplication.Exit(errorCode); |
| | 193 | | } |
| | 194 | |
|
| | 195 | | internal static void MarkFolderForAssetBundleBuild(string fullPath, string abName) |
| | 196 | | { |
| | 197 | | string assetPath = PathUtils.GetRelativePathTo(Application.dataPath, fullPath); |
| | 198 | | assetPath = Path.GetDirectoryName(assetPath); |
| | 199 | | AssetImporter importer = AssetImporter.GetAtPath(assetPath); |
| | 200 | | importer.SetAssetBundleNameAndVariant(abName, ""); |
| | 201 | | } |
| | 202 | |
|
| | 203 | | internal static void MarkAssetForAssetBundleBuild(IAssetDatabase assetDb, UnityEngine.Object asset, string abNam |
| | 204 | | { |
| | 205 | | string assetPath = PathUtils.GetRelativePathTo(Application.dataPath, assetDb.GetAssetPath(asset)); |
| | 206 | | var importer = AssetImporter.GetAtPath(assetPath); |
| | 207 | | importer.SetAssetBundleNameAndVariant(abName, ""); |
| | 208 | | } |
| | 209 | |
|
| | 210 | | public static MD5 md5 = new MD5CryptoServiceProvider(); |
| | 211 | |
|
| | 212 | | public static string CidToGuid(string cid) |
| | 213 | | { |
| | 214 | | byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(cid)); |
| | 215 | | StringBuilder sBuilder = new StringBuilder(); |
| | 216 | |
|
| | 217 | | for (int i = 0; i < data.Length; i++) |
| | 218 | | { |
| | 219 | | sBuilder.Append(data[i].ToString("x2")); |
| | 220 | | } |
| | 221 | |
|
| | 222 | | return sBuilder.ToString(); |
| | 223 | | } |
| | 224 | |
|
| | 225 | | public static HashSet<string> GetSceneCids(IWebRequest webRequest, ApiTLD tld, Vector2Int coords, Vector2Int siz |
| | 226 | | { |
| | 227 | | HashSet<string> sceneCids = new HashSet<string>(); |
| | 228 | |
|
| | 229 | | string url = GetScenesAPIUrl(tld, coords.x, coords.y, size.x, size.y); |
| | 230 | |
|
| | 231 | | DownloadHandler downloadHandler = null; |
| | 232 | |
|
| | 233 | | try |
| | 234 | | { |
| | 235 | | downloadHandler = webRequest.Get(url); |
| | 236 | | } |
| | 237 | | catch (HttpRequestException e) |
| | 238 | | { |
| | 239 | | throw new Exception($"Request error! Parcels couldn't be fetched! -- {e.Message}", e); |
| | 240 | | } |
| | 241 | |
|
| | 242 | | ScenesAPIData scenesApiData = JsonUtility.FromJson<ScenesAPIData>(downloadHandler.text); |
| | 243 | | downloadHandler.Dispose(); |
| | 244 | |
|
| | 245 | | Assert.IsTrue(scenesApiData != null, "Invalid response from ScenesAPI"); |
| | 246 | | Assert.IsTrue(scenesApiData.data != null, "Invalid response from ScenesAPI"); |
| | 247 | |
|
| | 248 | | foreach (var data in scenesApiData.data) |
| | 249 | | { |
| | 250 | | sceneCids.Add(data.root_cid); |
| | 251 | | } |
| | 252 | |
|
| | 253 | | return sceneCids; |
| | 254 | | } |
| | 255 | |
|
| | 256 | | public static HashSet<string> GetScenesCids(IWebRequest webRequest, ApiTLD tld, List<Vector2Int> coords) |
| | 257 | | { |
| | 258 | | HashSet<string> sceneCids = new HashSet<string>(); |
| | 259 | |
|
| | 260 | | foreach (Vector2Int v in coords) |
| | 261 | | { |
| | 262 | | string url = GetScenesAPIUrl(tld, v.x, v.y, 0, 0); |
| | 263 | |
|
| | 264 | | DownloadHandler downloadHandler = null; |
| | 265 | |
|
| | 266 | | try |
| | 267 | | { |
| | 268 | | downloadHandler = webRequest.Get(url); |
| | 269 | | } |
| | 270 | | catch (HttpRequestException e) |
| | 271 | | { |
| | 272 | | throw new HttpRequestException($"Request error! Parcels couldn't be fetched! -- {url} -- {e.Message} |
| | 273 | | } |
| | 274 | |
|
| | 275 | | ScenesAPIData scenesApiData = JsonUtility.FromJson<ScenesAPIData>(downloadHandler.text); |
| | 276 | | downloadHandler.Dispose(); |
| | 277 | |
|
| | 278 | | Assert.IsTrue(scenesApiData != null, "Invalid response from ScenesAPI"); |
| | 279 | | Assert.IsTrue(scenesApiData.data != null, "Invalid response from ScenesAPI"); |
| | 280 | |
|
| | 281 | | foreach (var data in scenesApiData.data) |
| | 282 | | { |
| | 283 | | sceneCids.Add(data.root_cid); |
| | 284 | | } |
| | 285 | | } |
| | 286 | |
|
| | 287 | | return sceneCids; |
| | 288 | | } |
| | 289 | |
|
| | 290 | | public static MappingsAPIData GetSceneMappingsData(IWebRequest webRequest, ApiTLD tld, string sceneCid) |
| | 291 | | { |
| | 292 | | string url = GetMappingsAPIUrl(tld, sceneCid); |
| | 293 | |
|
| | 294 | | DownloadHandler downloadHandler = null; |
| | 295 | |
|
| | 296 | | try |
| | 297 | | { |
| | 298 | | downloadHandler = webRequest.Get(url); |
| | 299 | | } |
| | 300 | | catch (HttpRequestException e) |
| | 301 | | { |
| | 302 | | throw new Exception($"Request error! mappings couldn't be fetched for scene {sceneCid}! -- {e.Message}") |
| | 303 | | } |
| | 304 | |
|
| | 305 | | MappingsAPIData parcelInfoApiData = JsonUtility.FromJson<MappingsAPIData>(downloadHandler.text); |
| | 306 | | downloadHandler.Dispose(); |
| | 307 | |
|
| | 308 | | if (parcelInfoApiData.data.Length == 0 || parcelInfoApiData.data == null) |
| | 309 | | { |
| | 310 | | throw new Exception("MappingsAPIData is null?"); |
| | 311 | | } |
| | 312 | |
|
| | 313 | | return parcelInfoApiData; |
| | 314 | | } |
| | 315 | |
|
| | 316 | | /// <summary> |
| | 317 | | /// Given a MappingPair list, returns a AssetPath list filtered by file extensions |
| | 318 | | /// </summary> |
| | 319 | | /// <param name="pairsToSearch">The MappingPair list to be filtered and converted</param> |
| | 320 | | /// <param name="extensions">An array detailing the extensions to filter them</param> |
| | 321 | | /// <returns>A dictionary that maps hashes to mapping pairs</returns> |
| | 322 | | public static List<AssetPath> GetPathsFromPairs(string basePath, MappingPair[] pairsToSearch, string[] extension |
| | 323 | | { |
| | 324 | | var tmpResult = new Dictionary<(string, string), AssetPath>(); |
| | 325 | |
|
| | 326 | | for (int i = 0; i < pairsToSearch.Length; i++) |
| | 327 | | { |
| | 328 | | MappingPair mappingPair = pairsToSearch[i]; |
| | 329 | |
|
| | 330 | | bool hasExtension = extensions.Any((x) => mappingPair.file.ToLower().EndsWith(x)); |
| | 331 | |
|
| | 332 | | if (hasExtension) |
| | 333 | | { |
| | 334 | | if (!tmpResult.ContainsKey((mappingPair.hash, mappingPair.file))) |
| | 335 | | tmpResult.Add((mappingPair.hash, mappingPair.file), new AssetPath(basePath, mappingPair)); |
| | 336 | | } |
| | 337 | | } |
| | 338 | |
|
| | 339 | | return tmpResult.Values.ToList(); |
| | 340 | | } |
| | 341 | |
|
| | 342 | | public static void FixGltfRootInvalidUriCharacters(GLTFRoot gltfRoot) |
| | 343 | | { |
| | 344 | | if (gltfRoot == null) |
| | 345 | | { |
| | 346 | | Debug.LogError("FixGltfRootInvalidUriCharacters >>> gltfRoot is null!"); |
| | 347 | | return; |
| | 348 | | } |
| | 349 | |
|
| | 350 | | GLTFRoot root = gltfRoot; |
| | 351 | |
|
| | 352 | | if (root.Images != null) |
| | 353 | | { |
| | 354 | | foreach (GLTFImage image in root.Images) |
| | 355 | | { |
| | 356 | | if (!string.IsNullOrEmpty(image.Uri)) |
| | 357 | | { |
| | 358 | | bool isBase64 = URIHelper.IsBase64Uri(image.Uri); |
| | 359 | |
|
| | 360 | | if (!isBase64) |
| | 361 | | { |
| | 362 | | image.Uri = image.Uri.Replace('/', Path.DirectorySeparatorChar); |
| | 363 | | } |
| | 364 | | } |
| | 365 | | } |
| | 366 | | } |
| | 367 | |
|
| | 368 | | if (root.Buffers != null) |
| | 369 | | { |
| | 370 | | foreach (GLTFBuffer buffer in root.Buffers) |
| | 371 | | { |
| | 372 | | if (!string.IsNullOrEmpty(buffer.Uri)) |
| | 373 | | { |
| | 374 | | bool isBase64 = URIHelper.IsBase64Uri(buffer.Uri); |
| | 375 | |
|
| | 376 | | if (!isBase64) |
| | 377 | | { |
| | 378 | | buffer.Uri = buffer.Uri.Replace('/', Path.DirectorySeparatorChar); |
| | 379 | | } |
| | 380 | | } |
| | 381 | | } |
| | 382 | | } |
| | 383 | | } |
| | 384 | |
|
| | 385 | | public static void CleanAssetBundleFolder(IFile file, string pathToSearch, string[] assetBundlesList, Dictionary |
| | 386 | | { |
| | 387 | | for (int i = 0; i < assetBundlesList.Length; i++) |
| | 388 | | { |
| | 389 | | if (string.IsNullOrEmpty(assetBundlesList[i])) |
| | 390 | | continue; |
| | 391 | |
|
| | 392 | | try |
| | 393 | | { |
| | 394 | | //NOTE(Brian): This is done for correctness sake, rename files to preserve the hash upper-case |
| | 395 | | if (lowerToUpperDictionary.TryGetValue(assetBundlesList[i], out string hashWithUppercase)) |
| | 396 | | { |
| | 397 | | string oldPath = pathToSearch + assetBundlesList[i]; |
| | 398 | | string path = pathToSearch + hashWithUppercase; |
| | 399 | | file.Move(oldPath, path); |
| | 400 | | } |
| | 401 | |
|
| | 402 | | string oldPathMf = pathToSearch + assetBundlesList[i] + ".manifest"; |
| | 403 | | file.Delete(oldPathMf); |
| | 404 | | } |
| | 405 | | catch (Exception e) |
| | 406 | | { |
| | 407 | | Debug.LogWarning("Error! " + e.Message); |
| | 408 | | } |
| | 409 | | } |
| | 410 | | } |
| | 411 | | } |
| | 412 | | } |