├── .gitignore ├── Dependancies ├── Ionic.Zip.dll └── System.Drawing.dll ├── ExporterHeader.png ├── ExporterSKFB.cs ├── GlTF_Accessor.cs ├── GlTF_AmbientLight.cs ├── GlTF_AnimSampler.cs ├── GlTF_Animation.cs ├── GlTF_Attributes.cs ├── GlTF_BufferView.cs ├── GlTF_Camera.cs ├── GlTF_Channel.cs ├── GlTF_ColorOrTexture.cs ├── GlTF_ColorRGB.cs ├── GlTF_ColorRGBA.cs ├── GlTF_DirectionalLight.cs ├── GlTF_ExporterWindow.cs ├── GlTF_FloatArray.cs ├── GlTF_FloatArray4.cs ├── GlTF_Image.cs ├── GlTF_Light.cs ├── GlTF_Material.cs ├── GlTF_MaterialColor.cs ├── GlTF_MaterialTexture.cs ├── GlTF_Matrix.cs ├── GlTF_Mesh.cs ├── GlTF_Node.cs ├── GlTF_Orthographic.cs ├── GlTF_Perspective.cs ├── GlTF_PointLight.cs ├── GlTF_Primitive.cs ├── GlTF_Program.cs ├── GlTF_Rotation.cs ├── GlTF_Sampler.cs ├── GlTF_Scale.cs ├── GlTF_Shader.cs ├── GlTF_Skin.cs ├── GlTF_SpotLight.cs ├── GlTF_Target.cs ├── GlTF_Technique.cs ├── GlTF_Texture.cs ├── GlTF_Translation.cs ├── GlTF_Vector3.cs ├── GlTF_Writer.cs ├── LICENSE ├── Preset.cs ├── README.md ├── Resources ├── ExporterHeader.png └── dropdown_menu.JPG ├── SceneToGlTFWiz.cs └── SimpleJSON.cs /.gitignore: -------------------------------------------------------------------------------- 1 | *.meta 2 | -------------------------------------------------------------------------------- /Dependancies/Ionic.Zip.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sketchfab/Unity-glTF-Exporter/c54295bd5ad40b5f4b99099119fff11d5e5ab92a/Dependancies/Ionic.Zip.dll -------------------------------------------------------------------------------- /Dependancies/System.Drawing.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sketchfab/Unity-glTF-Exporter/c54295bd5ad40b5f4b99099119fff11d5e5ab92a/Dependancies/System.Drawing.dll -------------------------------------------------------------------------------- /ExporterHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sketchfab/Unity-glTF-Exporter/c54295bd5ad40b5f4b99099119fff11d5e5ab92a/ExporterHeader.png -------------------------------------------------------------------------------- /ExporterSKFB.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using UnityEditor; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using SimpleJSON; 8 | using System.Runtime.Serialization.Formatters.Binary; 9 | using System; 10 | using UnityEditor.SceneManagement; 11 | 12 | public enum ExporterState 13 | { 14 | IDLE, 15 | REQUEST_CODE, 16 | PUBLISH_MODEL, 17 | GET_CATEGORIES, 18 | USER_ACCOUNT_TYPE, 19 | CHECK_VERSION 20 | } 21 | 22 | 23 | public class ExporterSKFB : EditorWindow { 24 | 25 | [MenuItem("Tools/Publish to Sketchfab")] 26 | static void Init() 27 | { 28 | #if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX // edit: added Platform Dependent Compilation - win or osx standalone 29 | ExporterSKFB window = (ExporterSKFB)EditorWindow.GetWindow(typeof(ExporterSKFB)); 30 | window.titleContent.text = "Sketchfab"; 31 | window.Show(); 32 | #else // and error dialog if not standalone 33 | EditorUtility.DisplayDialog("Error", "Your build target must be set to standalone", "Okay"); 34 | #endif 35 | } 36 | 37 | // Static data 38 | public static string skfbUrl = "https://sketchfab.com/"; 39 | public static string latestReleaseUrl = "https://github.com/sketchfab/Unity-glTF-Exporter/releases"; 40 | public static string resetPasswordUrl = "https://sketchfab.com/login/reset-password"; 41 | public static string createAccountUrl = "https://sketchfab.com/signup"; 42 | public static string reportAnIssueUrl = "https://help.sketchfab.com/hc/en-us/requests/new?type=exporters&subject=Unity+Exporter"; 43 | public static string privateUrl = "https://help.sketchfab.com/hc/en-us/articles/115000422206-Private-Models"; 44 | public static string draftUrl = "https://help.sketchfab.com/hc/en-us/articles/115000472906-Draft-Mode"; 45 | 46 | // UI dimensions (to be cleaned) 47 | [SerializeField] 48 | Vector2 loginSize = new Vector2(603, 190); 49 | [SerializeField] 50 | Vector2 fullSize = new Vector2(603, 690); 51 | [SerializeField] 52 | Vector2 descSize = new Vector2(603, 175); 53 | 54 | // Fields limits 55 | const int NAME_LIMIT = 48; 56 | const int DESC_LIMIT = 1024; 57 | const int TAGS_LIMIT = 50; 58 | const int PASSWORD_LIMIT = 64; 59 | const int SPACE_SIZE = 5; 60 | 61 | private string exporterVersion = GlTF_Writer.exporterVersion; 62 | private string latestVersion = "0.0.1"; 63 | 64 | // Keys used to save credentials in editor prefs 65 | const string usernameEditorKey = "UnityExporter_username"; 66 | //const string passwordEditorKey = "UnityExporter_password"; 67 | 68 | // Exporter UI: static elements 69 | [SerializeField] 70 | Texture2D header; 71 | GUIStyle exporterTextArea; 72 | GUIStyle exporterLabel; 73 | GUIStyle exporterClickableLabel; 74 | private string clickableLabelColor = "navy"; 75 | //private Color clickableLabelColor = 76 | // Exporter objects and scripts 77 | WWW www; 78 | string access_token = ""; 79 | ExporterState state; 80 | GameObject exporterGo; 81 | ExporterScript publisher; 82 | SceneToGlTFWiz exporter; 83 | private string exportPath; 84 | private string zipPath; 85 | 86 | ////Account settings 87 | 88 | 89 | //Fields 90 | private string user_name = ""; 91 | private string user_password = ""; 92 | 93 | private bool opt_exportAnimation = true; 94 | private string param_name = ""; 95 | private string param_description = ""; 96 | private string param_tags = ""; 97 | private bool param_autopublish = true; 98 | private bool param_private = false; 99 | private string param_password = ""; 100 | 101 | // Exporter UI: dynamic elements 102 | private string status = ""; 103 | private Color blueColor = new Color(69 / 255.0f, 185 / 255.0f, 223 / 255.0f); 104 | private Color redColor = new Color(0.8f, 0.0f, 0.0f); 105 | private Color greyColor = Color.white; 106 | private bool isUserPro = false; 107 | private string userDisplayName = ""; 108 | Dictionary categories = new Dictionary(); 109 | List categoriesNames = new List(); 110 | //int categoryIndex = 0; 111 | Rect windowRect; 112 | 113 | // Oauth stuff 114 | private float expiresIn = 0; 115 | private int lastTokenTime = 0; 116 | 117 | //private List tagList; 118 | void Awake() 119 | { 120 | zipPath = Application.temporaryCachePath + "/" + "Unity2Skfb.zip"; 121 | exportPath = Application.temporaryCachePath + "/" + "Unity2Skfb.gltf"; 122 | 123 | exporterGo = new GameObject("Exporter"); 124 | publisher = exporterGo.AddComponent(); 125 | exporter = exporterGo.AddComponent(); 126 | //FIXME: Make sure that object is deleted; 127 | exporterGo.hideFlags = HideFlags.HideAndDontSave; 128 | //publisher.getCategories(); 129 | resizeWindow(loginSize); 130 | publisher.checkVersion(); 131 | } 132 | 133 | void OnEnable() 134 | { 135 | // Try to load header image 136 | if(!header) 137 | header = (Texture2D)Resources.Load(Application.dataPath + "/Unity-glTF-Exporter/ExporterHeader.png", typeof(Texture2D)); 138 | 139 | // Pre-fill model name with scene name if empty 140 | if (param_name.Length == 0) 141 | { 142 | param_name = EditorSceneManager.GetActiveScene().name; 143 | } 144 | resizeWindow(loginSize); 145 | relog(); 146 | } 147 | 148 | int convertToSeconds(DateTime time) 149 | { 150 | return (int)(time.Hour * 3600 + time.Minute * 60 + time.Second); 151 | } 152 | 153 | void OnSelectionChange() 154 | { 155 | // do nothing for now 156 | } 157 | 158 | void resizeWindow(Vector2 size) 159 | { 160 | //this.maxSize = size; 161 | this.minSize = size; 162 | } 163 | 164 | void relog() 165 | { 166 | if(publisher && publisher.getState() == ExporterState.REQUEST_CODE) 167 | { 168 | return; 169 | } 170 | if (user_name.Length == 0) 171 | { 172 | user_name = EditorPrefs.GetString(usernameEditorKey); 173 | //user_password = EditorPrefs.GetString(passwordEditorKey); 174 | } 175 | 176 | if (publisher && user_name.Length > 0 && user_password.Length > 0) 177 | { 178 | publisher.oauth(user_name, user_password); 179 | } 180 | } 181 | 182 | void expandWindow(bool expand) 183 | { 184 | windowRect = this.position; 185 | windowRect.height = expand ? fullSize.y : loginSize.y; 186 | position = windowRect; 187 | } 188 | 189 | // Update is called once per frame 190 | void OnInspectorUpdate() 191 | { 192 | Repaint(); 193 | float currentTimeSecond = convertToSeconds(DateTime.Now); 194 | if (access_token.Length > 0 && currentTimeSecond - lastTokenTime > expiresIn) 195 | { 196 | access_token = ""; 197 | relog(); 198 | } 199 | 200 | if (publisher != null && publisher.www != null && publisher.www.isDone) 201 | { 202 | state = publisher.getState(); 203 | www = publisher.www; 204 | switch (state) 205 | { 206 | case ExporterState.CHECK_VERSION: 207 | JSONNode githubResponse = JSON.Parse(this.jsonify(www.text)); 208 | if(githubResponse != null && githubResponse[0]["tag_name"] != null) 209 | { 210 | latestVersion = githubResponse[0]["tag_name"]; 211 | if (exporterVersion != latestVersion) 212 | { 213 | bool update = EditorUtility.DisplayDialog("Exporter update", "A new version is available \n(you have version " + exporterVersion + ")\nIt's strongly rsecommended that you update now. The latest version may include important bug fixes and improvements", "Update", "Skip"); 214 | if (update) 215 | { 216 | Application.OpenURL(latestReleaseUrl); 217 | } 218 | } 219 | else 220 | { 221 | resizeWindow(fullSize); 222 | } 223 | } 224 | else 225 | { 226 | latestVersion = ""; 227 | resizeWindow(fullSize + new Vector2(0, 15)); 228 | } 229 | publisher.setIdle(); 230 | break; 231 | case ExporterState.REQUEST_CODE: 232 | JSONNode accessResponse = JSON.Parse(this.jsonify(www.text)); 233 | if (accessResponse["access_token"] != null) 234 | { 235 | access_token = accessResponse["access_token"]; 236 | expiresIn = accessResponse["expires_in"].AsFloat; 237 | lastTokenTime = convertToSeconds(DateTime.Now); 238 | publisher.getAccountType(access_token); 239 | if (exporterVersion != latestVersion) 240 | resizeWindow(fullSize + new Vector2(0, 20)); 241 | else 242 | resizeWindow(fullSize); 243 | } 244 | else 245 | { 246 | string errorDesc = accessResponse["error_description"]; 247 | EditorUtility.DisplayDialog("Authentication failed", "Failed to authenticate on Sketchfab.com.\nPlease check your credentials\n\nError: " + errorDesc, "Ok"); 248 | publisher.setIdle(); 249 | } 250 | 251 | break; 252 | case ExporterState.PUBLISH_MODEL: 253 | //foreach(string key in www.responseHeaders.Keys) 254 | //{ 255 | // Debug.Log("[" + key + "] = " + www.responseHeaders[key]); 256 | //} 257 | if (www.responseHeaders["STATUS"].Contains("201") == true) 258 | { 259 | string urlid = www.responseHeaders["LOCATION"].Split('/')[www.responseHeaders["LOCATION"].Split('/').Length -1]; 260 | string url = skfbUrl + "models/" + urlid; 261 | Application.OpenURL(url); 262 | } 263 | else 264 | { 265 | EditorUtility.DisplayDialog("Upload failed", www.responseHeaders["STATUS"], "Ok"); 266 | } 267 | publisher.setIdle(); 268 | break; 269 | case ExporterState.GET_CATEGORIES: 270 | string jsonify = this.jsonify(www.text); 271 | if (!jsonify.Contains("results")) 272 | { 273 | Debug.Log(jsonify); 274 | Debug.Log("Failed to retrieve categories"); 275 | publisher.setIdle(); 276 | break; 277 | } 278 | 279 | JSONArray categoriesArray = JSON.Parse(jsonify)["results"].AsArray; 280 | foreach (JSONNode node in categoriesArray) 281 | { 282 | categories.Add(node["name"], node["slug"]); 283 | categoriesNames.Add(node["name"]); 284 | } 285 | publisher.setIdle(); 286 | break; 287 | 288 | case ExporterState.USER_ACCOUNT_TYPE: 289 | string accountRequest = this.jsonify(www.text); 290 | if(!accountRequest.Contains("account")) 291 | { 292 | Debug.Log(accountRequest); 293 | Debug.Log("Failed to retrieve user account type"); 294 | publisher.setIdle(); 295 | break; 296 | } 297 | 298 | var userSettings = JSON.Parse(accountRequest); 299 | isUserPro = userSettings["account"].ToString().Contains("free") == false; 300 | userDisplayName = userSettings["displayName"]; 301 | publisher.setIdle(); 302 | break; 303 | } 304 | } 305 | } 306 | 307 | private string jsonify(string jsondata) 308 | { 309 | return jsondata.Replace("null", "\"null\""); 310 | } 311 | 312 | public float progress() 313 | { 314 | if (www == null) 315 | return 0.0f; 316 | 317 | return 0.99f * www.uploadProgress + 0.01f * www.progress; 318 | } 319 | 320 | private bool updateExporterStatus() 321 | { 322 | status = ""; 323 | 324 | if (param_name.Length > NAME_LIMIT) 325 | { 326 | status = "Model name is too long"; 327 | return false; 328 | } 329 | 330 | 331 | if (param_name.Length == 0) 332 | { 333 | status = "Please give a name to your model"; 334 | return false; 335 | } 336 | 337 | 338 | if (param_description.Length > DESC_LIMIT) 339 | { 340 | status = "Model description is too long"; 341 | return false; 342 | } 343 | 344 | 345 | if (param_tags.Length > TAGS_LIMIT) 346 | { 347 | status = "Model tags are too long"; 348 | return false; 349 | } 350 | 351 | 352 | int nbSelectedObjects = Selection.GetTransforms(SelectionMode.Deep).Length; 353 | if (nbSelectedObjects == 0) 354 | { 355 | status = "No object selected to export"; 356 | return false; 357 | } 358 | 359 | status = "Upload " + nbSelectedObjects + " object" + (nbSelectedObjects != 1 ? "s" : ""); 360 | return true; 361 | } 362 | 363 | void OnGUI() 364 | { 365 | if(exporterLabel == null) 366 | { 367 | exporterLabel = new GUIStyle(GUI.skin.label); 368 | exporterLabel.richText = true; 369 | } 370 | 371 | if(exporterTextArea == null) 372 | { 373 | exporterTextArea = new GUIStyle(GUI.skin.textArea); 374 | exporterTextArea.fixedWidth = descSize.x; 375 | exporterTextArea.fixedHeight = descSize.y; 376 | } 377 | 378 | if(exporterClickableLabel == null) 379 | { 380 | exporterClickableLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel); 381 | exporterClickableLabel.richText = true; 382 | } 383 | //Header 384 | GUILayout.BeginHorizontal(); 385 | GUILayout.FlexibleSpace(); 386 | GUILayout.Label(header); 387 | GUILayout.FlexibleSpace(); 388 | GUILayout.EndHorizontal(); 389 | 390 | // Account settings 391 | if (access_token.Length == 0) 392 | { 393 | GUILayout.Label("Log in with your Sketchfab account", EditorStyles.centeredGreyMiniLabel); 394 | user_name = EditorGUILayout.TextField("Email", user_name); 395 | user_password = EditorGUILayout.PasswordField("Password", user_password); 396 | GUILayout.BeginHorizontal(); 397 | GUILayout.FlexibleSpace(); 398 | if(GUILayout.Button("Create an account - ", exporterClickableLabel, GUILayout.Height(20))) 399 | { 400 | Application.OpenURL(createAccountUrl); 401 | } 402 | if (GUILayout.Button("Reset your password - ", exporterClickableLabel, GUILayout.Height(20))) 403 | { 404 | Application.OpenURL(resetPasswordUrl); 405 | } 406 | if (GUILayout.Button("Report an issue", exporterClickableLabel, GUILayout.Height(20))) 407 | { 408 | Application.OpenURL(reportAnIssueUrl); 409 | } 410 | GUILayout.EndHorizontal(); 411 | GUILayout.BeginHorizontal(); 412 | GUILayout.FlexibleSpace(); 413 | if (GUILayout.Button("Login", GUILayout.Width(150), GUILayout.Height(25))) 414 | { 415 | www = publisher.www; 416 | publisher.oauth(user_name, user_password); 417 | EditorPrefs.SetString(usernameEditorKey, user_name); 418 | //EditorPrefs.SetString(passwordEditorKey, user_password); 419 | } 420 | GUILayout.EndHorizontal(); 421 | } 422 | else 423 | { 424 | if (latestVersion.Length == 0) 425 | { 426 | 427 | Color current = GUI.color; 428 | GUI.color = Color.red; 429 | GUILayout.Label("An error occured when looking for the latest exporter version\nYou might be using an old and not fully supported version", EditorStyles.centeredGreyMiniLabel); 430 | if (GUILayout.Button("Click here to be redirected to release page")) 431 | { 432 | Application.OpenURL(latestReleaseUrl); 433 | } 434 | GUI.color = current; 435 | } 436 | else if (exporterVersion != latestVersion) 437 | { 438 | Color current = GUI.color; 439 | GUI.color = redColor; 440 | GUILayout.Label("New version " + latestVersion + " available (current version is " + exporterVersion + ")", EditorStyles.centeredGreyMiniLabel); 441 | GUILayout.BeginHorizontal(); 442 | GUILayout.FlexibleSpace(); 443 | if (GUILayout.Button("Go to release page", GUILayout.Width(150), GUILayout.Height(25))) 444 | { 445 | Application.OpenURL(latestReleaseUrl); 446 | } 447 | GUILayout.FlexibleSpace(); 448 | GUILayout.EndHorizontal(); 449 | GUI.color = current; 450 | } 451 | else 452 | { 453 | GUILayout.BeginHorizontal(); 454 | GUILayout.Label("Exporter is up to date (version:" + exporterVersion + ")", EditorStyles.centeredGreyMiniLabel); 455 | 456 | GUILayout.FlexibleSpace(); 457 | if(GUILayout.Button("Help -", exporterClickableLabel, GUILayout.Height(20))) 458 | { 459 | Application.OpenURL(latestReleaseUrl); 460 | } 461 | 462 | if (GUILayout.Button("Report an issue", exporterClickableLabel, GUILayout.Height(20))) 463 | { 464 | Application.OpenURL(reportAnIssueUrl); 465 | } 466 | GUILayout.EndHorizontal(); 467 | } 468 | GUILayout.BeginHorizontal("Box"); 469 | GUILayout.Label("Account: " + userDisplayName + " (" + (isUserPro ? "PRO" : "FREE") + " account)", exporterLabel); 470 | if (GUILayout.Button("Logout")) 471 | { 472 | access_token = ""; 473 | //EditorPrefs.DeleteKey(usernameEditorKey); 474 | //EditorPrefs.DeleteKey(passwordEditorKey); 475 | resizeWindow(loginSize); 476 | } 477 | GUILayout.EndHorizontal(); 478 | } 479 | 480 | GUILayout.Space(SPACE_SIZE); 481 | 482 | if (access_token.Length > 0) 483 | { 484 | // Model settings 485 | GUILayout.Label("Model properties", EditorStyles.boldLabel); 486 | 487 | // Model name 488 | GUILayout.Label("Name"); 489 | param_name = EditorGUILayout.TextField(param_name); 490 | GUILayout.Label("(" + param_name.Length + "/" + NAME_LIMIT + ")", EditorStyles.centeredGreyMiniLabel); 491 | EditorStyles.textField.wordWrap = true; 492 | GUILayout.Space(SPACE_SIZE); 493 | 494 | GUILayout.Label("Description"); 495 | param_description = EditorGUILayout.TextArea(param_description, exporterTextArea); 496 | GUILayout.Label("(" + param_description.Length + " / 1024)", EditorStyles.centeredGreyMiniLabel); 497 | GUILayout.Space(SPACE_SIZE); 498 | GUILayout.Label("Tags (separated by spaces)"); 499 | param_tags = EditorGUILayout.TextField(param_tags); 500 | GUILayout.Label("'unity' and 'unity3D' added automatically (" + param_tags.Length + "/50)", EditorStyles.centeredGreyMiniLabel); 501 | GUILayout.Space(SPACE_SIZE); 502 | // ENable only if user is pro 503 | 504 | GUILayout.Label("PRO only features", EditorStyles.centeredGreyMiniLabel); 505 | if (isUserPro) { 506 | EditorGUILayout.BeginVertical("Box"); 507 | GUILayout.BeginHorizontal(); 508 | param_private = EditorGUILayout.Toggle("Private model", param_private); 509 | if (GUILayout.Button("(more info)", exporterClickableLabel, GUILayout.Height(20))) 510 | { 511 | Application.OpenURL(latestReleaseUrl); 512 | } 513 | GUILayout.FlexibleSpace(); 514 | GUILayout.EndHorizontal(); 515 | GUI.enabled = isUserPro && param_private; 516 | GUILayout.Label("Password"); 517 | param_password = EditorGUILayout.TextField(param_password); 518 | EditorGUILayout.EndVertical(); 519 | GUI.enabled = true; 520 | } 521 | GUILayout.Label("Options", EditorStyles.boldLabel); 522 | GUILayout.BeginHorizontal(); 523 | opt_exportAnimation = EditorGUILayout.Toggle("Export animation (beta)", opt_exportAnimation); 524 | GUILayout.FlexibleSpace(); 525 | GUILayout.EndHorizontal(); 526 | 527 | GUILayout.BeginHorizontal(); 528 | param_autopublish = EditorGUILayout.Toggle("Publish immediately ", param_autopublish); 529 | if (GUILayout.Button("(more info)", exporterClickableLabel, GUILayout.Height(20))) 530 | { 531 | Application.OpenURL(latestReleaseUrl); 532 | } 533 | GUILayout.FlexibleSpace(); 534 | GUILayout.EndHorizontal(); 535 | //GUILayout.Space(SPACE_SIZE); 536 | 537 | //if (categories.Count > 0) 538 | // categoryIndex = EditorGUILayout.Popup(categoryIndex, categoriesNames.ToArray()); 539 | 540 | //GUILayout.Space(SPACE_SIZE); 541 | bool enable = updateExporterStatus(); 542 | 543 | if (enable) 544 | GUI.color = blueColor; 545 | else 546 | GUI.color = greyColor; 547 | 548 | if (publisher != null && publisher.getState() == ExporterState.PUBLISH_MODEL && publisher.www != null) 549 | { 550 | Rect r = EditorGUILayout.BeginVertical(); 551 | EditorGUI.ProgressBar(r, progress(), "Upload progress"); 552 | GUILayout.Space(18); 553 | EditorGUILayout.EndVertical(); 554 | } 555 | else 556 | { 557 | GUI.enabled = enable; 558 | GUILayout.BeginHorizontal(); 559 | GUILayout.FlexibleSpace(); 560 | if (GUILayout.Button(status, GUILayout.Width(250), GUILayout.Height(40))) 561 | { 562 | if (!enable) 563 | { 564 | EditorUtility.DisplayDialog("Error", status, "Ok"); 565 | } 566 | else 567 | { 568 | if (System.IO.File.Exists(zipPath)) 569 | { 570 | System.IO.File.Delete(zipPath); 571 | } 572 | 573 | exporter.ExportCoroutine(exportPath, null, true, true, opt_exportAnimation, true); 574 | 575 | if (File.Exists(zipPath)) 576 | { 577 | publisher.setFilePath(zipPath); 578 | publisher.publish(buildParameterDict(), access_token); 579 | www = publisher.www; 580 | } 581 | else 582 | { 583 | Debug.Log("Zip file has not been generated. Aborting publish."); 584 | } 585 | } 586 | } 587 | GUILayout.FlexibleSpace(); 588 | GUILayout.EndHorizontal(); 589 | } 590 | } 591 | } 592 | 593 | private Dictionary buildParameterDict() 594 | { 595 | Dictionary parameters = new Dictionary(); 596 | parameters["name"] = param_name; 597 | parameters["description"] = param_description; 598 | parameters["tags"] = "unity unity3D " + param_tags; 599 | parameters["private"] = param_private ? "1" : "0"; 600 | parameters["isPublished"] = param_autopublish ? "1" : "0"; 601 | //string category = categories[categoriesNames[categoryIndex]]; 602 | //Debug.Log(category); 603 | //parameters["categories"] = category; 604 | if (param_private) 605 | parameters["password"] = param_password; 606 | 607 | return parameters; 608 | } 609 | 610 | void OnDestroy() 611 | { 612 | if (System.IO.File.Exists(zipPath)) 613 | System.IO.File.Delete(zipPath); 614 | 615 | if (exporterGo) 616 | { 617 | DestroyImmediate(exporterGo); 618 | exporter = null; 619 | publisher = null; 620 | } 621 | } 622 | } 623 | 624 | public class ExporterScript : MonoBehaviour 625 | { 626 | bool done = false; 627 | ExporterState state; 628 | public WWW www; 629 | public string localFileName = ""; 630 | private string skfbUrl = "https://sketchfab.com/"; 631 | private string latestVersionCheckUrl = "https://api.github.com/repos/sketchfab/Unity-glTF-Exporter/releases"; 632 | 633 | public void Start() 634 | { 635 | state = ExporterState.IDLE; 636 | done = false; 637 | } 638 | 639 | public bool isDone() 640 | { 641 | return done; 642 | } 643 | public void checkVersion() 644 | { 645 | StartCoroutine(checkVersionCoroutine()); 646 | } 647 | 648 | public void oauth(string user_name, string user_password) 649 | { 650 | StartCoroutine(oauthCoroutine(user_name, user_password)); 651 | } 652 | 653 | public void publish(Dictionary para, string accessToken) 654 | { 655 | StartCoroutine(publishCoroutine(para, accessToken)); 656 | } 657 | 658 | public void setState(ExporterState newState) 659 | { 660 | this.state = newState; 661 | } 662 | 663 | public ExporterState getState() 664 | { 665 | return state; 666 | } 667 | 668 | public void setIdle() 669 | { 670 | state = ExporterState.IDLE; 671 | } 672 | public void setFilePath(string exportFilepath) 673 | { 674 | localFileName = exportFilepath; 675 | } 676 | 677 | public void getCategories() 678 | { 679 | StartCoroutine(categoriesCoroutine()); 680 | } 681 | 682 | public void getAccountType(string access_token) 683 | { 684 | StartCoroutine(userAccountCoroutine(access_token)); 685 | } 686 | 687 | private IEnumerator categoriesCoroutine() 688 | { 689 | state = ExporterState.GET_CATEGORIES; 690 | www = new WWW(skfbUrl + "v3/categories"); 691 | yield return www; 692 | } 693 | 694 | string dummyClientId = "IUO8d5VVOIUCzWQArQ3VuXfbwx5QekZfLeDlpOmW"; 695 | 696 | // Request access_token 697 | private IEnumerator oauthCoroutine(string user_name, string user_password) 698 | { 699 | done = false; 700 | state = ExporterState.REQUEST_CODE; 701 | WWWForm oform = new WWWForm(); 702 | oform.AddField("username", user_name); 703 | oform.AddField("password", user_password); 704 | www = new WWW(skfbUrl + "oauth2/token/?grant_type=password&client_id=" + dummyClientId, oform); 705 | yield return www; 706 | } 707 | 708 | private IEnumerator checkVersionCoroutine() 709 | { 710 | state = ExporterState.CHECK_VERSION; 711 | www = new WWW(latestVersionCheckUrl); 712 | yield return www; 713 | } 714 | 715 | private IEnumerator userAccountCoroutine(string access_token) 716 | { 717 | done = false; 718 | state = ExporterState.USER_ACCOUNT_TYPE; 719 | WWWForm oform = new WWWForm(); 720 | Dictionary headers = oform.headers; 721 | headers["Authorization"] = "Bearer " + access_token; 722 | www = new WWW(skfbUrl + "v3/me", null, headers); 723 | yield return www; 724 | } 725 | 726 | // Publish file to Sketchfab 727 | private IEnumerator publishCoroutine(Dictionary parameters, string accessToken) 728 | { 729 | state = ExporterState.PUBLISH_MODEL; 730 | done = false; 731 | WWWForm postForm = new WWWForm(); 732 | if (!System.IO.File.Exists(localFileName)) 733 | { 734 | Debug.LogError("File has not been exported. Aborting publish."); 735 | } 736 | 737 | // Export 738 | byte[] data = File.ReadAllBytes(localFileName); 739 | postForm.AddBinaryData("modelFile", data, localFileName, "application/zip"); 740 | foreach (string param in parameters.Keys) 741 | { 742 | postForm.AddField(param, parameters[param]); 743 | } 744 | 745 | postForm.AddField("source", "Unity-exporter"); 746 | //postForm.AddField("isPublished", true ? "1" : "0"); 747 | //postForm.AddField("private", true ? "1" : "0"); 748 | 749 | Dictionary headers = postForm.headers; 750 | headers["Authorization"] = "Bearer " + accessToken; 751 | www = new WWW(skfbUrl + "v3/models", postForm.data, headers); 752 | yield return www; 753 | } 754 | } 755 | #endif -------------------------------------------------------------------------------- /GlTF_Accessor.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Accessor : GlTF_Writer { 6 | public enum Type { 7 | SCALAR, 8 | VEC2, 9 | VEC3, 10 | VEC4, 11 | MAT4 12 | } 13 | 14 | public enum ComponentType { 15 | BYTE = 5120, 16 | UNSIGNED_BYTE = 5121, 17 | SHORT = 5122, 18 | USHORT = 5123, 19 | UNSIGNED_INT = 5125, 20 | FLOAT = 5126, 21 | } 22 | 23 | public GlTF_BufferView bufferView;// "bufferView": "bufferView_30", 24 | public int bufferViewIndex; 25 | public long byteOffset; //": 0, 26 | public ComponentType componentType; // GL enum vals ": BYTE (5120), UNSIGNED_BYTE (5121), SHORT (5122), UNSIGNED_SHORT (5123), FLOAT (5126) 27 | public int count;//": 2399, 28 | public Type type = Type.SCALAR; 29 | public Vector2 scaleValues; 30 | public Vector2 offsetValues; 31 | 32 | Vector4 maxFloat; 33 | Vector4 minFloat; 34 | Matrix4x4 minMatrix = new Matrix4x4(); 35 | Matrix4x4 maxMatrix = new Matrix4x4(); 36 | int minInt; 37 | int maxInt; 38 | 39 | public GlTF_Accessor (string n) { id = n; } 40 | public GlTF_Accessor (string n, Type t, ComponentType c) { 41 | id = n; 42 | type = t; 43 | componentType = c; 44 | } 45 | 46 | public static string GetNameFromObject(Object o, string name) 47 | { 48 | return "accessor_" + name + "_"+ GlTF_Writer.GetNameFromObject(o, true); 49 | } 50 | 51 | void InitMinMaxInt() 52 | { 53 | maxInt = int.MinValue; 54 | minInt = int.MaxValue; 55 | } 56 | 57 | void InitMinMaxFloat() 58 | { 59 | float min = float.MinValue; 60 | float max = float.MaxValue; 61 | maxFloat = new Vector4(min, min, min, min); 62 | minFloat = new Vector4(max, max, max, max); 63 | } 64 | 65 | public void PopulateWithOffsetScale(Vector2[] v2s, bool flip) 66 | { 67 | Vector2[] uv2 = v2s; 68 | for(int i=0; i< uv2.Length; ++i) 69 | { 70 | float u = uv2[i][0] * scaleValues[0] + offsetValues[0]; 71 | float v = uv2[i][1] * scaleValues[1] + offsetValues[1]; 72 | uv2[i] = new Vector2(u, v); 73 | } 74 | 75 | Populate(uv2, flip); 76 | } 77 | 78 | public void Populate (int[] vs, bool flippedTriangle) 79 | { 80 | if (type != Type.SCALAR) 81 | throw (new System.Exception()); 82 | byteOffset = bufferView.currentOffset; 83 | bufferView.Populate (vs, flippedTriangle); 84 | count = vs.Length; 85 | if (count > 0) 86 | { 87 | InitMinMaxInt(); 88 | for (int i = 0; i < count; ++i) 89 | { 90 | minInt = Mathf.Min(vs[i], minInt); 91 | maxInt = Mathf.Max(vs[i], maxInt); 92 | } 93 | } 94 | } 95 | 96 | public void Populate (float[] vs) 97 | { 98 | if (type != Type.SCALAR) 99 | throw (new System.Exception()); 100 | 101 | byteOffset = bufferView.currentOffset; 102 | bufferView.Populate (vs); 103 | count = vs.Length; 104 | if (count > 0) 105 | { 106 | InitMinMaxFloat(); 107 | for (int i = 0; i < count; ++i) 108 | { 109 | minFloat.x = Mathf.Min(vs[i], minFloat.x); 110 | maxFloat.x = Mathf.Max(vs[i], maxFloat.x); 111 | } 112 | } 113 | } 114 | 115 | public void Populate (Vector2[] v2s, bool flip = false) 116 | { 117 | if (type != Type.VEC2) 118 | throw (new System.Exception()); 119 | byteOffset = bufferView.currentOffset; 120 | count = v2s.Length; 121 | if (count > 0) 122 | { 123 | InitMinMaxFloat(); 124 | 125 | if (flip) 126 | { 127 | for (int i = 0; i < v2s.Length; i++) 128 | { 129 | bufferView.Populate (v2s[i].x); 130 | float y = 1.0f - v2s[i].y; 131 | bufferView.Populate (y); 132 | minFloat.x = Mathf.Min(v2s[i].x, minFloat.x); 133 | minFloat.y = Mathf.Min(y, minFloat.y); 134 | maxFloat.x = Mathf.Max(v2s[i].x, maxFloat.x); 135 | maxFloat.y = Mathf.Max(y, maxFloat.y); 136 | } 137 | } else { 138 | for (int i = 0; i < v2s.Length; i++) 139 | { 140 | bufferView.Populate (v2s[i].x); 141 | bufferView.Populate (v2s[i].y); 142 | minFloat.x = Mathf.Min(v2s[i].x, minFloat.x); 143 | minFloat.y = Mathf.Min(v2s[i].y, minFloat.y); 144 | maxFloat.x = Mathf.Max(v2s[i].x, maxFloat.x); 145 | maxFloat.y = Mathf.Max(v2s[i].y, maxFloat.y); 146 | } 147 | } 148 | } 149 | } 150 | 151 | public void Populate (Vector3[] v3s, bool noConvert=false) 152 | { 153 | if (type != Type.VEC3) 154 | throw (new System.Exception()); 155 | byteOffset = bufferView.currentOffset; 156 | count = v3s.Length; 157 | 158 | if (count > 0) 159 | { 160 | InitMinMaxFloat(); 161 | 162 | for (int i = 0; i < v3s.Length; i++) 163 | { 164 | if (convertRightHanded && !noConvert) 165 | convertVector3LeftToRightHandedness(ref v3s[i]); 166 | 167 | bufferView.Populate (v3s[i].x); 168 | bufferView.Populate (v3s[i].y); 169 | bufferView.Populate (v3s[i].z); 170 | 171 | minFloat.x = Mathf.Min(v3s[i].x, minFloat.x); 172 | minFloat.y = Mathf.Min(v3s[i].y, minFloat.y); 173 | minFloat.z = Mathf.Min(v3s[i].z, minFloat.z); 174 | maxFloat.x = Mathf.Max(v3s[i].x, maxFloat.x); 175 | maxFloat.y = Mathf.Max(v3s[i].y, maxFloat.y); 176 | maxFloat.z = Mathf.Max(v3s[i].z, maxFloat.z); 177 | } 178 | } 179 | } 180 | 181 | public void PopulateShort(Vector4[] v4s, bool noConvert = true, bool useUInt = false) 182 | { 183 | if (type != Type.VEC4) 184 | throw (new System.Exception()); 185 | 186 | byteOffset = bufferView.currentOffset; 187 | 188 | count = v4s.Length; 189 | if (count > 0) 190 | { 191 | InitMinMaxFloat(); 192 | for (int i = 0; i < v4s.Length; i++) 193 | { 194 | bufferView.PopulateShort((ushort)v4s[i].x); 195 | bufferView.PopulateShort((ushort)v4s[i].y); 196 | bufferView.PopulateShort((ushort)v4s[i].z); 197 | bufferView.PopulateShort((ushort)v4s[i].w); 198 | 199 | minFloat.x = Mathf.Min(v4s[i].x, minFloat.x); 200 | minFloat.y = Mathf.Min(v4s[i].y, minFloat.y); 201 | minFloat.z = Mathf.Min(v4s[i].z, minFloat.z); 202 | minFloat.w = Mathf.Min(v4s[i].w, minFloat.w); 203 | maxFloat.x = Mathf.Max(v4s[i].x, maxFloat.x); 204 | maxFloat.y = Mathf.Max(v4s[i].y, maxFloat.y); 205 | maxFloat.z = Mathf.Max(v4s[i].z, maxFloat.z); 206 | maxFloat.w = Mathf.Max(v4s[i].w, maxFloat.w); 207 | } 208 | } 209 | 210 | } 211 | 212 | public void Populate (Vector4[] v4s, bool noConvert = true, bool useUInt = false) 213 | { 214 | if (type != Type.VEC4) 215 | throw (new System.Exception()); 216 | 217 | byteOffset = bufferView.currentOffset; 218 | 219 | count = v4s.Length; 220 | if (count > 0) 221 | { 222 | InitMinMaxFloat(); 223 | for (int i = 0; i < v4s.Length; i++) 224 | { 225 | if (convertRightHanded && !noConvert) 226 | convertVector4LeftToRightHandedness(ref v4s[i]); 227 | 228 | if (useUInt) 229 | { 230 | bufferView.Populate((uint)v4s[i].x); 231 | bufferView.Populate((uint)v4s[i].y); 232 | bufferView.Populate((uint)v4s[i].z); 233 | bufferView.Populate((uint)v4s[i].w); 234 | } 235 | else 236 | { 237 | bufferView.Populate(v4s[i].x); 238 | bufferView.Populate(v4s[i].y); 239 | bufferView.Populate(v4s[i].z); 240 | bufferView.Populate(v4s[i].w); 241 | } 242 | 243 | minFloat.x = Mathf.Min(v4s[i].x, minFloat.x); 244 | minFloat.y = Mathf.Min(v4s[i].y, minFloat.y); 245 | minFloat.z = Mathf.Min(v4s[i].z, minFloat.z); 246 | minFloat.w = Mathf.Min(v4s[i].w, minFloat.w); 247 | maxFloat.x = Mathf.Max(v4s[i].x, maxFloat.x); 248 | maxFloat.y = Mathf.Max(v4s[i].y, maxFloat.y); 249 | maxFloat.z = Mathf.Max(v4s[i].z, maxFloat.z); 250 | maxFloat.w = Mathf.Max(v4s[i].w, maxFloat.w); 251 | } 252 | } 253 | 254 | } 255 | 256 | public void Populate(Color[] colors) 257 | { 258 | if (type != Type.VEC4) 259 | throw (new System.Exception()); 260 | 261 | byteOffset = bufferView.currentOffset; 262 | 263 | count = colors.Length; 264 | if (count > 0) 265 | { 266 | InitMinMaxFloat(); 267 | for (int i = 0; i < colors.Length; i++) 268 | { 269 | bufferView.Populate(colors[i].r); 270 | bufferView.Populate(colors[i].g); 271 | bufferView.Populate(colors[i].b); 272 | bufferView.Populate(colors[i].a); 273 | minFloat.x = Mathf.Min(colors[i].r, minFloat.x); 274 | minFloat.y = Mathf.Min(colors[i].g, minFloat.y); 275 | minFloat.z = Mathf.Min(colors[i].b, minFloat.z); 276 | minFloat.w = Mathf.Min(colors[i].a, minFloat.w); 277 | maxFloat.x = Mathf.Max(colors[i].r, maxFloat.x); 278 | maxFloat.y = Mathf.Max(colors[i].g, maxFloat.y); 279 | maxFloat.z = Mathf.Max(colors[i].b, maxFloat.z); 280 | maxFloat.w = Mathf.Max(colors[i].a, maxFloat.w); 281 | } 282 | } 283 | 284 | } 285 | 286 | public void Populate(Matrix4x4[] matrices, Transform m) 287 | { 288 | if (type != Type.MAT4) 289 | throw (new System.Exception()); 290 | 291 | byteOffset = bufferView.currentOffset; 292 | count = matrices.Length; 293 | if(count > 0) 294 | { 295 | for(int i = 0; i < matrices.Length; i++) 296 | { 297 | Matrix4x4 mat = matrices[i]; 298 | 299 | // This code is buggy, don't use it for now. 300 | //if (convertRightHanded) 301 | // convertMatrixLeftToRightHandedness(ref mat); 302 | 303 | for (int j = 0; j < 4; j++) 304 | { 305 | for(int k=0; k < 4; k++) 306 | { 307 | // Matrices in unity are column major 308 | // as for Gltf 309 | float value = mat[k, j]; 310 | bufferView.Populate(value); 311 | minMatrix[k, j] = Mathf.Min(value, minMatrix[k, j]); 312 | maxMatrix[k, j] = Mathf.Max(value, maxMatrix[k, j]); 313 | } 314 | } 315 | } 316 | } 317 | } 318 | 319 | void WriteMin() 320 | { 321 | if (componentType == ComponentType.FLOAT) 322 | { 323 | switch (type) 324 | { 325 | case Type.SCALAR: 326 | jsonWriter.Write (minFloat.x); 327 | break; 328 | 329 | case Type.VEC2: 330 | jsonWriter.Write (minFloat.x + ", " + minFloat.y); 331 | break; 332 | 333 | case Type.VEC3: 334 | jsonWriter.Write (minFloat.x + ", " + minFloat.y + ", " + minFloat.z); 335 | break; 336 | 337 | case Type.VEC4: 338 | jsonWriter.Write (minFloat.x + ", " + minFloat.y + ", " + minFloat.z + ", " + minFloat.w); 339 | break; 340 | case Type.MAT4: 341 | for (int i = 0; i < 15; ++i) 342 | { 343 | jsonWriter.Write(minMatrix[i] + ", "); 344 | } 345 | jsonWriter.Write(minMatrix[15]); 346 | break; 347 | } 348 | } 349 | else if (componentType == ComponentType.USHORT || componentType == ComponentType.UNSIGNED_INT) 350 | { 351 | if (type == Type.SCALAR) 352 | { 353 | jsonWriter.Write(minInt); 354 | } 355 | else if (type == Type.VEC4) 356 | { 357 | jsonWriter.Write((int)minFloat.x + ", " + (int)minFloat.y + ", " + (int)minFloat.z + ", " + (int)minFloat.w); 358 | } 359 | } 360 | } 361 | 362 | void WriteMax() 363 | { 364 | if (componentType == ComponentType.FLOAT) 365 | { 366 | switch (type) 367 | { 368 | case Type.SCALAR: 369 | jsonWriter.Write (maxFloat.x); 370 | break; 371 | 372 | case Type.VEC2: 373 | jsonWriter.Write (maxFloat.x + ", " + maxFloat.y); 374 | break; 375 | 376 | case Type.VEC3: 377 | jsonWriter.Write (maxFloat.x + ", " + maxFloat.y + ", " + maxFloat.z); 378 | break; 379 | 380 | case Type.VEC4: 381 | jsonWriter.Write (maxFloat.x + ", " + maxFloat.y + ", " + maxFloat.z + ", " + maxFloat.w); 382 | break; 383 | case Type.MAT4: 384 | for(int i=0; i < 15; ++i) 385 | { 386 | jsonWriter.Write(maxMatrix[i] + ", "); 387 | } 388 | jsonWriter.Write(maxMatrix[15]); 389 | break; 390 | } 391 | } 392 | else if (componentType == ComponentType.USHORT || componentType == ComponentType.UNSIGNED_INT) 393 | { 394 | if (type == Type.SCALAR) 395 | { 396 | jsonWriter.Write(maxInt); 397 | } 398 | else if(type == Type.VEC4) 399 | { 400 | jsonWriter.Write((int)maxFloat.x + ", " + (int)maxFloat.y + ", " + (int)maxFloat.z + ", " + (int)maxFloat.w); 401 | } 402 | } 403 | } 404 | 405 | public override void Write () 406 | { 407 | Indent(); jsonWriter.Write ("{\n"); 408 | IndentIn(); 409 | Indent(); jsonWriter.Write ("\"bufferView\": " + bufferViews.IndexOf(bufferView) +",\n"); 410 | Indent(); jsonWriter.Write ("\"byteOffset\": " + byteOffset + ",\n"); 411 | Indent(); jsonWriter.Write ("\"componentType\": " + (int)componentType + ",\n"); 412 | Indent(); jsonWriter.Write ("\"count\": " + count + ",\n"); 413 | 414 | Indent(); jsonWriter.Write("\"max\": [ "); 415 | WriteMax(); 416 | jsonWriter.Write(" ],\n"); 417 | Indent(); jsonWriter.Write("\"min\": [ "); 418 | WriteMin(); 419 | jsonWriter.Write(" ],\n"); 420 | 421 | Indent(); jsonWriter.Write ("\"type\": \"" + type + "\"\n"); 422 | IndentOut(); 423 | Indent(); jsonWriter.Write ("}"); 424 | } 425 | } 426 | #endif -------------------------------------------------------------------------------- /GlTF_AmbientLight.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_AmbientLight : GlTF_Light { 6 | public override void Write() 7 | { 8 | color.Write(); 9 | } 10 | } 11 | #endif -------------------------------------------------------------------------------- /GlTF_AnimSampler.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_AnimSampler : GlTF_Writer { 6 | public int input = -1; // accessor index 7 | public string interpolation = "LINEAR"; // Can also be STEP in glTF 2.0 8 | public int output = -1; // accessor index 9 | 10 | public GlTF_AnimSampler(int i, int o) { input = i; output = o; } 11 | public override void Write() 12 | { 13 | Indent(); jsonWriter.Write ("{\n"); 14 | IndentIn(); 15 | Indent(); jsonWriter.Write ("\"input\": " + input + ",\n"); 16 | Indent(); jsonWriter.Write ("\"interpolation\": \"" + interpolation + "\",\n"); 17 | Indent(); jsonWriter.Write ("\"output\": " + output + "\n"); 18 | IndentOut(); 19 | Indent(); jsonWriter.Write ("}"); 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /GlTF_Animation.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEditor; 6 | 7 | public class GlTF_Animation : GlTF_Writer { 8 | public List channels = new List(); 9 | public List animSamplers = new List(); 10 | 11 | public enum ROTATION_TYPE 12 | { 13 | UNKNOWN, 14 | QUATERNION, 15 | EULER 16 | }; 17 | 18 | int bakingFramerate = 30; // FPS 19 | 20 | public GlTF_Animation (string n) { 21 | name = n; 22 | } 23 | 24 | private struct TargetCurveSet 25 | { 26 | public AnimationCurve[] translationCurves; 27 | public AnimationCurve[] rotationCurves; 28 | //Additional curve types 29 | public AnimationCurve[] localEulerAnglesRaw; 30 | public AnimationCurve[] m_LocalEuler; 31 | public AnimationCurve[] scaleCurves; 32 | public ROTATION_TYPE rotationType; 33 | public void Init() 34 | { 35 | translationCurves = new AnimationCurve[3]; 36 | rotationCurves = new AnimationCurve[4]; 37 | scaleCurves = new AnimationCurve[3]; 38 | } 39 | } 40 | 41 | public void Populate(AnimationClip clip, Transform tr, bool bake = true) 42 | { 43 | // 1. browse clip, collect all curves and create a TargetCurveSet for each target 44 | Dictionary targetCurvesBinding = new Dictionary(); 45 | collectClipCurves(clip, ref targetCurvesBinding); 46 | 47 | // Baking needs all properties, fill missing curves with transform data in 2 keyframes (start, endTime) 48 | // where endTime is clip duration 49 | generateMissingCurves(clip.length, ref tr, ref targetCurvesBinding); 50 | 51 | if (bake) 52 | { 53 | // Bake animation for all animated nodes 54 | foreach (string target in targetCurvesBinding.Keys) 55 | { 56 | Transform targetTr = target.Length > 0 ? tr.Find(target) : tr; 57 | if (targetTr == null) 58 | continue; 59 | 60 | Transform targetObject = targetTr; 61 | string targetId = GlTF_Node.GetNameFromObject(targetObject); 62 | 63 | // Initialize accessors for current animation 64 | GlTF_Accessor timeAccessor = new GlTF_Accessor(targetId + "_TimeAccessor_" + clip.name, GlTF_Accessor.Type.SCALAR, GlTF_Accessor.ComponentType.FLOAT); 65 | timeAccessor.bufferView = GlTF_Writer.floatBufferView; 66 | int timeAccessorIndex = GlTF_Writer.accessors.Count; 67 | GlTF_Writer.accessors.Add(timeAccessor); 68 | 69 | // Translation 70 | GlTF_Channel chTranslation = new GlTF_Channel("translation", animSamplers.Count); 71 | GlTF_Target targetTranslation = new GlTF_Target(); 72 | targetTranslation.id = targetId; 73 | targetTranslation.path = "translation"; 74 | chTranslation.target = targetTranslation; 75 | channels.Add(chTranslation); 76 | 77 | GlTF_AnimSampler sTranslation = new GlTF_AnimSampler(timeAccessorIndex, GlTF_Writer.accessors.Count); 78 | GlTF_Accessor translationAccessor = new GlTF_Accessor(targetId + "_TranslationAccessor_" + clip.name, GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); 79 | translationAccessor.bufferView = GlTF_Writer.vec3BufferViewAnim; 80 | GlTF_Writer.accessors.Add(translationAccessor); 81 | animSamplers.Add(sTranslation); 82 | 83 | // Rotation 84 | GlTF_Channel chRotation = new GlTF_Channel("rotation", animSamplers.Count); 85 | GlTF_Target targetRotation = new GlTF_Target(); 86 | targetRotation.id = GlTF_Node.GetNameFromObject(targetObject); 87 | targetRotation.path = "rotation"; 88 | chRotation.target = targetRotation; 89 | channels.Add(chRotation); 90 | 91 | GlTF_AnimSampler sRotation = new GlTF_AnimSampler(timeAccessorIndex, GlTF_Writer.accessors.Count); 92 | GlTF_Accessor rotationAccessor = new GlTF_Accessor(targetId + "_RotationAccessor_" + clip.name, GlTF_Accessor.Type.VEC4, GlTF_Accessor.ComponentType.FLOAT); 93 | rotationAccessor.bufferView = GlTF_Writer.vec4BufferViewAnim; 94 | GlTF_Writer.accessors.Add(rotationAccessor); 95 | animSamplers.Add(sRotation); 96 | 97 | // Scale 98 | GlTF_Channel chScale = new GlTF_Channel("scale", animSamplers.Count); 99 | GlTF_Target targetScale = new GlTF_Target(); 100 | targetScale.id = GlTF_Node.GetNameFromObject(targetObject); 101 | targetScale.path = "scale"; 102 | chScale.target = targetScale; 103 | channels.Add(chScale); 104 | 105 | GlTF_AnimSampler sScale = new GlTF_AnimSampler(timeAccessorIndex, GlTF_Writer.accessors.Count); 106 | GlTF_Accessor scaleAccessor = new GlTF_Accessor(targetId + "_ScaleAccessor_" + clip.name, GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); 107 | scaleAccessor.bufferView = GlTF_Writer.vec3BufferViewAnim; 108 | GlTF_Writer.accessors.Add(scaleAccessor); 109 | animSamplers.Add(sScale); 110 | 111 | // Bake and populate animation data 112 | float[] times = null; 113 | Vector3[] positions = null; 114 | Vector3[] scales = null; 115 | Vector4[] rotations = null; 116 | bakeCurveSet(targetCurvesBinding[target], clip.length, bakingFramerate, ref times, ref positions, ref rotations, ref scales); 117 | 118 | // Populate accessors 119 | timeAccessor.Populate(times); 120 | translationAccessor.Populate(positions); 121 | rotationAccessor.Populate(rotations, false); 122 | scaleAccessor.Populate(scales, true); 123 | } 124 | } 125 | else 126 | { 127 | Debug.LogError("Only baked animation is supported for now. Skipping animation"); 128 | } 129 | 130 | } 131 | 132 | private void collectClipCurves(AnimationClip clip, ref Dictionary targetCurves) 133 | { 134 | foreach (var binding in AnimationUtility.GetCurveBindings(clip)) 135 | { 136 | AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding); 137 | 138 | if (!targetCurves.ContainsKey(binding.path)) 139 | { 140 | TargetCurveSet curveSet = new TargetCurveSet(); 141 | curveSet.Init(); 142 | targetCurves.Add(binding.path, curveSet); 143 | } 144 | 145 | TargetCurveSet current = targetCurves[binding.path]; 146 | if (binding.propertyName.Contains("m_LocalPosition")) 147 | { 148 | if (binding.propertyName.Contains(".x")) 149 | current.translationCurves[0] = curve; 150 | else if (binding.propertyName.Contains(".y")) 151 | current.translationCurves[1] = curve; 152 | else if (binding.propertyName.Contains(".z")) 153 | current.translationCurves[2] = curve; 154 | } 155 | else if (binding.propertyName.Contains("m_LocalScale")) 156 | { 157 | if (binding.propertyName.Contains(".x")) 158 | current.scaleCurves[0] = curve; 159 | else if (binding.propertyName.Contains(".y")) 160 | current.scaleCurves[1] = curve; 161 | else if (binding.propertyName.Contains(".z")) 162 | current.scaleCurves[2] = curve; 163 | } 164 | else if (binding.propertyName.ToLower().Contains("localrotation")) 165 | { 166 | current.rotationType = ROTATION_TYPE.QUATERNION; 167 | if (binding.propertyName.Contains(".x")) 168 | current.rotationCurves[0] = curve; 169 | else if (binding.propertyName.Contains(".y")) 170 | current.rotationCurves[1] = curve; 171 | else if (binding.propertyName.Contains(".z")) 172 | current.rotationCurves[2] = curve; 173 | else if (binding.propertyName.Contains(".w")) 174 | current.rotationCurves[3] = curve; 175 | } 176 | // Takes into account 'localEuler', 'localEulerAnglesBaked' and 'localEulerAnglesRaw' 177 | else if (binding.propertyName.ToLower().Contains("localeuler")) 178 | { 179 | current.rotationType = ROTATION_TYPE.EULER; 180 | if (binding.propertyName.Contains(".x")) 181 | current.rotationCurves[0] = curve; 182 | else if (binding.propertyName.Contains(".y")) 183 | current.rotationCurves[1] = curve; 184 | else if (binding.propertyName.Contains(".z")) 185 | current.rotationCurves[2] = curve; 186 | } 187 | targetCurves[binding.path] = current; 188 | } 189 | } 190 | 191 | private void generateMissingCurves(float endTime, ref Transform tr, ref Dictionary targetCurvesBinding) 192 | { 193 | foreach (string target in targetCurvesBinding.Keys) 194 | { 195 | Transform targetTr = target.Length > 0 ? tr.Find(target): tr; 196 | if (targetTr == null) 197 | continue; 198 | 199 | TargetCurveSet current = targetCurvesBinding[target]; 200 | if (current.translationCurves[0] == null) 201 | { 202 | current.translationCurves[0] = createConstantCurve(targetTr.localPosition.x, endTime); 203 | current.translationCurves[1] = createConstantCurve(targetTr.localPosition.y, endTime); 204 | current.translationCurves[2] = createConstantCurve(targetTr.localPosition.z, endTime); 205 | } 206 | 207 | if (current.scaleCurves[0] == null) 208 | { 209 | current.scaleCurves[0] = createConstantCurve(targetTr.localScale.x, endTime); 210 | current.scaleCurves[1] = createConstantCurve(targetTr.localScale.y, endTime); 211 | current.scaleCurves[2] = createConstantCurve(targetTr.localScale.z, endTime); 212 | } 213 | 214 | if (current.rotationCurves[0] == null) 215 | { 216 | current.rotationCurves[0] = createConstantCurve(targetTr.localRotation.x, endTime); 217 | current.rotationCurves[1] = createConstantCurve(targetTr.localRotation.y, endTime); 218 | current.rotationCurves[2] = createConstantCurve(targetTr.localRotation.z, endTime); 219 | current.rotationCurves[3] = createConstantCurve(targetTr.localRotation.w, endTime); 220 | } 221 | } 222 | } 223 | 224 | private void bakeCurveSet(TargetCurveSet curveSet, float length, int bakingFramerate, ref float[] times, ref Vector3[] positions, ref Vector4[] rotations, ref Vector3[] scales) 225 | { 226 | int nbSamples = (int)(length * 30); 227 | float deltaTime = length / nbSamples; 228 | 229 | // Initialize Arrays 230 | times = new float[nbSamples]; 231 | positions = new Vector3[nbSamples]; 232 | scales = new Vector3[nbSamples]; 233 | rotations = new Vector4[nbSamples]; 234 | 235 | // Assuming all the curves exist now 236 | for (int i = 0; i < nbSamples; ++i) 237 | { 238 | float currentTime = i * deltaTime; 239 | times[i] = currentTime; 240 | positions[i] = new Vector3(curveSet.translationCurves[0].Evaluate(currentTime), curveSet.translationCurves[1].Evaluate(currentTime), curveSet.translationCurves[2].Evaluate(currentTime)); 241 | scales[i] = new Vector3(curveSet.scaleCurves[0].Evaluate(currentTime), curveSet.scaleCurves[1].Evaluate(currentTime), curveSet.scaleCurves[2].Evaluate(currentTime)); 242 | if(curveSet.rotationType == ROTATION_TYPE.EULER) 243 | { 244 | Quaternion eulerToQuat = Quaternion.Euler(curveSet.rotationCurves[0].Evaluate(currentTime), curveSet.rotationCurves[1].Evaluate(currentTime), curveSet.rotationCurves[2].Evaluate(currentTime)); 245 | rotations[i] = new Vector4(eulerToQuat.x, eulerToQuat.y, eulerToQuat.z, eulerToQuat.w); 246 | } 247 | else 248 | { 249 | rotations[i] = new Vector4(curveSet.rotationCurves[0].Evaluate(currentTime), curveSet.rotationCurves[1].Evaluate(currentTime), curveSet.rotationCurves[2].Evaluate(currentTime), curveSet.rotationCurves[3].Evaluate(currentTime)); 250 | } 251 | } 252 | } 253 | 254 | public AnimationCurve createConstantCurve(float value, float endTime) 255 | { 256 | // No translation curves, adding them 257 | AnimationCurve curve = new AnimationCurve(); 258 | curve.AddKey(0, value); 259 | curve.AddKey(endTime, value); 260 | return curve; 261 | } 262 | 263 | public override void Write() 264 | { 265 | if (channels.Count == 0) 266 | return; 267 | 268 | Indent(); jsonWriter.Write ("{\n"); 269 | IndentIn(); 270 | Indent(); jsonWriter.Write("\"name\": \"" + name + "\",\n"); 271 | Indent(); jsonWriter.Write ("\"channels\": [\n"); 272 | foreach (GlTF_Channel c in channels) 273 | { 274 | CommaNL(); 275 | c.Write (); 276 | } 277 | jsonWriter.WriteLine(); 278 | Indent(); jsonWriter.Write ("],\n"); 279 | 280 | Indent(); jsonWriter.Write ("\"samplers\": [\n"); 281 | IndentIn(); 282 | foreach (GlTF_AnimSampler s in animSamplers) 283 | { 284 | CommaNL(); 285 | s.Write (); 286 | } 287 | IndentOut(); 288 | jsonWriter.WriteLine(); 289 | Indent(); jsonWriter.Write ("]\n"); 290 | 291 | IndentOut(); 292 | Indent(); jsonWriter.Write ("}"); 293 | } 294 | } 295 | #endif -------------------------------------------------------------------------------- /GlTF_Attributes.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Attributes : GlTF_Writer { 6 | public GlTF_Accessor normalAccessor; 7 | public GlTF_Accessor positionAccessor; 8 | public GlTF_Accessor colorAccessor; 9 | public GlTF_Accessor texCoord0Accessor; 10 | public GlTF_Accessor texCoord1Accessor; 11 | public GlTF_Accessor texCoord2Accessor; 12 | public GlTF_Accessor texCoord3Accessor; 13 | public GlTF_Accessor lightmapTexCoordAccessor; 14 | public GlTF_Accessor jointAccessor; 15 | public GlTF_Accessor weightAccessor; 16 | public GlTF_Accessor tangentAccessor; 17 | 18 | private Vector4[] boneWeightToBoneVec4(BoneWeight[] bw) 19 | { 20 | Vector4[] bones = new Vector4[bw.Length]; 21 | for (int i=0; i < bw.Length; ++i) 22 | { 23 | bones[i] = new Vector4(bw[i].boneIndex0, bw[i].boneIndex1, bw[i].boneIndex2, bw[i].boneIndex3); 24 | } 25 | 26 | return bones; 27 | } 28 | 29 | private Vector4[] boneWeightToWeightVec4(BoneWeight[] bw) 30 | { 31 | Vector4[] weights = new Vector4[bw.Length]; 32 | for (int i = 0; i < bw.Length; ++i) 33 | { 34 | weights[i] = new Vector4(bw[i].weight0, bw[i].weight1, bw[i].weight2, bw[i].weight3); 35 | } 36 | 37 | return weights; 38 | } 39 | 40 | public void Populate (Mesh m) 41 | { 42 | positionAccessor.Populate (m.vertices); 43 | if(colorAccessor != null) 44 | { 45 | colorAccessor.Populate(m.colors); 46 | } 47 | if (normalAccessor != null) 48 | { 49 | normalAccessor.Populate (m.normals); 50 | } 51 | if (texCoord0Accessor != null) 52 | { 53 | texCoord0Accessor.Populate (m.uv, false); 54 | } 55 | if (texCoord1Accessor != null) 56 | { 57 | texCoord1Accessor.Populate (m.uv2, false); 58 | } 59 | if (texCoord2Accessor != null) 60 | { 61 | texCoord2Accessor.Populate (m.uv3, false); 62 | } 63 | if (texCoord3Accessor != null) 64 | { 65 | texCoord3Accessor.Populate (m.uv4, false); 66 | } 67 | if(lightmapTexCoordAccessor != null) 68 | { 69 | lightmapTexCoordAccessor.PopulateWithOffsetScale(m.uv2, false); 70 | } 71 | if(jointAccessor != null) 72 | { 73 | Vector4[] bones = boneWeightToBoneVec4(m.boneWeights); 74 | jointAccessor.PopulateShort(bones); 75 | } 76 | if(weightAccessor != null) 77 | { 78 | Vector4[] weights = boneWeightToWeightVec4(m.boneWeights); 79 | weightAccessor.Populate(weights); 80 | } 81 | if(tangentAccessor != null) 82 | { 83 | tangentAccessor.Populate(m.tangents, false); 84 | } 85 | } 86 | 87 | public override void Write () 88 | { 89 | Indent(); jsonWriter.Write ("\"attributes\": {\n"); 90 | IndentIn(); 91 | if (positionAccessor != null) 92 | { 93 | CommaNL(); 94 | Indent(); jsonWriter.Write ("\"POSITION\": " + GlTF_Writer.accessors.IndexOf(positionAccessor)); 95 | } 96 | if (normalAccessor != null) 97 | { 98 | CommaNL(); 99 | Indent(); jsonWriter.Write ("\"NORMAL\": " + GlTF_Writer.accessors.IndexOf(normalAccessor)); 100 | } 101 | if (colorAccessor != null) 102 | { 103 | CommaNL(); 104 | Indent(); jsonWriter.Write("\"COLOR_0\": " + GlTF_Writer.accessors.IndexOf(colorAccessor)); 105 | } 106 | if (texCoord0Accessor != null) 107 | { 108 | CommaNL(); 109 | Indent(); jsonWriter.Write ("\"TEXCOORD_0\": " + GlTF_Writer.accessors.IndexOf(texCoord0Accessor)); 110 | } 111 | if (texCoord1Accessor != null) 112 | { 113 | CommaNL(); 114 | Indent(); jsonWriter.Write ("\"TEXCOORD_1\": " + GlTF_Writer.accessors.IndexOf(texCoord1Accessor)); 115 | } 116 | if (texCoord2Accessor != null) 117 | { 118 | CommaNL(); 119 | Indent(); jsonWriter.Write ("\"TEXCOORD_2\": " + GlTF_Writer.accessors.IndexOf(texCoord2Accessor)); 120 | } 121 | if (texCoord3Accessor != null) 122 | { 123 | CommaNL(); 124 | Indent(); jsonWriter.Write ("\"TEXCOORD_3\": " + GlTF_Writer.accessors.IndexOf(texCoord3Accessor)); 125 | } 126 | if (lightmapTexCoordAccessor != null) 127 | { 128 | CommaNL(); 129 | Indent(); jsonWriter.Write("\"TEXCOORD_4\": " + GlTF_Writer.accessors.IndexOf(lightmapTexCoordAccessor)); 130 | } 131 | if (jointAccessor != null) 132 | { 133 | CommaNL(); 134 | Indent(); jsonWriter.Write("\"JOINTS_0\": " + GlTF_Writer.accessors.IndexOf(jointAccessor)); 135 | } 136 | if (weightAccessor != null) 137 | { 138 | CommaNL(); 139 | Indent(); jsonWriter.Write("\"WEIGHTS_0\": " + GlTF_Writer.accessors.IndexOf(weightAccessor)); 140 | } 141 | if (tangentAccessor != null) 142 | { 143 | CommaNL(); 144 | Indent(); jsonWriter.Write("\"TANGENT\": " + GlTF_Writer.accessors.IndexOf(tangentAccessor)); 145 | } 146 | 147 | jsonWriter.WriteLine(); 148 | IndentOut(); 149 | Indent(); jsonWriter.Write ("}"); 150 | } 151 | 152 | } 153 | #endif -------------------------------------------------------------------------------- /GlTF_BufferView.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.IO; 5 | using System; 6 | 7 | public class GlTF_BufferView : GlTF_Writer { 8 | 9 | public enum TARGET 10 | { 11 | ARRAY=34962, 12 | ELEMENT=34963 13 | } 14 | 15 | public int bufferIndex = 0;// ": "duck", 16 | public long byteLength;//": 25272, 17 | public long byteOffset;//": 0, 18 | public long byteStride; 19 | public int target= -1; 20 | // public string target = "ARRAY_BUFFER"; 21 | public int currentOffset = 0; 22 | public MemoryStream memoryStream = new MemoryStream(); 23 | public bool bin = false; 24 | 25 | public GlTF_BufferView (string n, int s) { name = n; byteStride = s; } 26 | public GlTF_BufferView (string n, int s, int t) { name = n; byteStride = s; target = t; } 27 | 28 | public void Populate (int[] vs, bool flippedTriangle) 29 | { 30 | if (flippedTriangle) 31 | { 32 | for (int i = 0; i < vs.Length; i+=3) 33 | { 34 | ushort u = (ushort)vs[i]; 35 | memoryStream.Write (BitConverter.GetBytes(u), 0, BitConverter.GetBytes(u).Length); 36 | currentOffset += 2; 37 | 38 | u = (ushort)vs[i+2]; 39 | memoryStream.Write (BitConverter.GetBytes(u), 0, BitConverter.GetBytes(u).Length); 40 | currentOffset += 2; 41 | 42 | u = (ushort)vs[i+1]; 43 | memoryStream.Write (BitConverter.GetBytes(u), 0, BitConverter.GetBytes(u).Length); 44 | currentOffset += 2; 45 | } 46 | } 47 | else 48 | { 49 | for (int i = 0; i < vs.Length; i++) 50 | { 51 | ushort u = (ushort)vs[i]; 52 | memoryStream.Write (BitConverter.GetBytes(u), 0, BitConverter.GetBytes(u).Length); 53 | currentOffset += 2; 54 | } 55 | } 56 | byteLength = currentOffset; 57 | } 58 | 59 | public void PopulateShort(ushort vs) 60 | { 61 | ushort u = (ushort)vs; 62 | memoryStream.Write(BitConverter.GetBytes(u), 0, BitConverter.GetBytes(u).Length); 63 | currentOffset += 2; 64 | byteLength += 2 ; 65 | } 66 | 67 | public void Populate (float[] vs) 68 | { 69 | for (int i = 0; i < vs.Length; i++) 70 | { 71 | // memoryStream.Write (vs[i]); 72 | // memoryStream.Write ((byte[])vs, 0, vs.Length * sizeof(int)); 73 | float f = vs[i]; 74 | memoryStream.Write (BitConverter.GetBytes(f), 0, BitConverter.GetBytes(f).Length); 75 | currentOffset += 4; 76 | } 77 | byteLength = currentOffset; 78 | } 79 | 80 | public void Populate(uint v) 81 | { 82 | memoryStream.Write(BitConverter.GetBytes(v), 0, BitConverter.GetBytes(v).Length); 83 | currentOffset += 4; 84 | byteLength = currentOffset; 85 | } 86 | 87 | public void Populate (float v) 88 | { 89 | memoryStream.Write (BitConverter.GetBytes(v), 0, BitConverter.GetBytes(v).Length); 90 | currentOffset += 4; 91 | byteLength = currentOffset; 92 | } 93 | 94 | public override void Write () 95 | { 96 | /* 97 | "bufferView_4642": { 98 | "buffer": "vc.bin", 99 | "byteLength": 630080, 100 | "byteOffset": 0, 101 | "target": "ARRAY_BUFFER" 102 | }, 103 | */ 104 | Indent(); jsonWriter.Write ("{\n"); 105 | IndentIn(); 106 | //var binName = binary ? "binary_glTF" : Path.GetFileNameWithoutExtension(GlTF_Writer.binFileName); 107 | Indent(); jsonWriter.Write ("\"buffer\": " + bufferIndex +",\n"); 108 | Indent(); jsonWriter.Write ("\"byteLength\": " + byteLength + ",\n"); 109 | if ((int)target != (int)-1) 110 | { 111 | Indent(); jsonWriter.Write("\"target\": " + target + ",\n"); 112 | } 113 | 114 | if (byteStride >= 4) 115 | { 116 | Indent(); jsonWriter.Write("\"byteStride\": " + byteStride + ",\n"); 117 | } 118 | 119 | Indent(); jsonWriter.Write ("\"byteOffset\": " + byteOffset + "\n"); 120 | 121 | IndentOut(); 122 | Indent(); jsonWriter.Write ("}"); 123 | } 124 | } 125 | #endif -------------------------------------------------------------------------------- /GlTF_Camera.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Camera : GlTF_Writer { 6 | public string type;// should be enum ": "perspective" 7 | } 8 | #endif -------------------------------------------------------------------------------- /GlTF_Channel.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Channel : GlTF_Writer { 6 | public int samplerIndex = -1; 7 | public GlTF_Target target; 8 | 9 | public GlTF_Channel (string ch, int sIndex) { 10 | samplerIndex = sIndex; 11 | } 12 | 13 | public override void Write() 14 | { 15 | if(samplerIndex == -1) 16 | { 17 | Debug.LogError("Error when serializing gltf Channel for target: " + target.id); 18 | return; 19 | } 20 | 21 | IndentIn(); 22 | Indent(); jsonWriter.Write ("{\n"); 23 | IndentIn(); 24 | Indent(); jsonWriter.Write ("\"sampler\": " + samplerIndex + ",\n"); 25 | target.Write (); 26 | jsonWriter.WriteLine(); 27 | IndentOut(); 28 | Indent(); jsonWriter.Write ("}"); 29 | IndentOut(); 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /GlTF_ColorOrTexture.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_ColorOrTexture : GlTF_Writer { 6 | public GlTF_ColorOrTexture() {} 7 | public GlTF_ColorOrTexture (string n) { name = n; } 8 | } 9 | #endif -------------------------------------------------------------------------------- /GlTF_ColorRGB.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_ColorRGB : GlTF_Writer { 6 | Color color; 7 | public GlTF_ColorRGB (string n) { name = n; } 8 | public GlTF_ColorRGB (Color c) { color = c; } 9 | public GlTF_ColorRGB (string n, Color c) { name = n; color = c; } 10 | public override void Write () 11 | { 12 | Indent(); 13 | if (name.Length > 0) 14 | jsonWriter.Write ("\"" + name + "\": "); 15 | else 16 | jsonWriter.Write ("\"color\": ["); 17 | jsonWriter.Write (color.r.ToString() + ", " + color.g.ToString() + ", " +color.b.ToString()+"]"); 18 | } 19 | } 20 | #endif -------------------------------------------------------------------------------- /GlTF_ColorRGBA.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_ColorRGBA : GlTF_Writer { 6 | Color color; 7 | public GlTF_ColorRGBA (string n) { name = n; } 8 | public GlTF_ColorRGBA (Color c) { color = c; } 9 | public GlTF_ColorRGBA (string n, Color c) { name = n; color = c; } 10 | public override void Write () 11 | { 12 | Indent(); 13 | if (name.Length > 0) 14 | jsonWriter.Write ("\"" + name + "\": ["); 15 | else 16 | jsonWriter.Write ("\"color\": ["); 17 | jsonWriter.Write (color.r.ToString() + ", " + color.g.ToString() + ", " +color.b.ToString()+ ", " +color.a.ToString()+"]"); 18 | } 19 | } 20 | #endif -------------------------------------------------------------------------------- /GlTF_DirectionalLight.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_DirectionalLight : GlTF_Light { 6 | public override void Write() 7 | { 8 | color.Write(); 9 | } 10 | } 11 | #endif -------------------------------------------------------------------------------- /GlTF_ExporterWindow.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEditor; 6 | using System.IO; 7 | using System.Xml; 8 | 9 | public class GlTFExporterWindow : EditorWindow 10 | { 11 | const string KEY_PATH = "GlTFPath"; 12 | const string KEY_FILE = "GlTFFile"; 13 | static public string path = "?"; 14 | static string savedPath; 15 | static string savedFile; 16 | static XmlDocument xdoc; 17 | 18 | static Preset preset = new Preset(); 19 | static UnityEngine.TextAsset presetAsset; 20 | GameObject exporterGo; 21 | SceneToGlTFWiz exporter; 22 | bool buildZip = false; 23 | bool convertImages = false; 24 | bool exportAnimation = true; 25 | 26 | //EditorPrefs.SetString(KEY_PATH, savedPath); 27 | //EditorPrefs.SetString(KEY_FILE, savedFile); 28 | //[MenuItem("Tools/Export to glTF")] 29 | static void CreateWizard() 30 | { 31 | savedPath = EditorPrefs.GetString(KEY_PATH, "/"); 32 | savedFile = EditorPrefs.GetString(KEY_FILE, "test.gltf"); 33 | path = savedPath + "/" + savedFile; 34 | // ScriptableWizard.DisplayWizard("Export Selected Stuff to glTF", typeof(SceneToGlTFWiz), "Export"); 35 | 36 | GlTFExporterWindow window = (GlTFExporterWindow)EditorWindow.GetWindow(typeof(GlTFExporterWindow)); 37 | window.Show(); 38 | } 39 | 40 | void OnWizardUpdate() 41 | { 42 | // Texture[] txs = Selection.GetFiltered(Texture, SelectionMode.Assets); 43 | // Debug.Log("found "+txs.Length); 44 | } 45 | 46 | void OnGUI() 47 | { 48 | GUILayout.Label("Export Options"); 49 | GlTF_Writer.binary = GUILayout.Toggle(GlTF_Writer.binary, "Binary GlTF"); 50 | buildZip = GUILayout.Toggle(buildZip, "Export Zip"); 51 | 52 | // Force animation baking for now 53 | GlTF_Writer.bakeAnimation = GUILayout.Toggle(true, "Bake animations (forced for now)"); 54 | exportAnimation = GUILayout.Toggle(exportAnimation, "Export animations"); 55 | convertImages = GUILayout.Toggle(convertImages, "Convert images"); 56 | presetAsset = EditorGUILayout.ObjectField("Preset file", presetAsset, typeof(UnityEngine.TextAsset), false) as UnityEngine.TextAsset; 57 | if (!exporterGo) 58 | { 59 | exporterGo = new GameObject("exporter"); 60 | } 61 | if(!exporter) 62 | { 63 | exporter = exporterGo.AddComponent(); 64 | } 65 | GUI.enabled = (Selection.GetTransforms(SelectionMode.Deep).Length > 0); 66 | if (GUILayout.Button("Export to glTF")) 67 | { 68 | ExportFile(); 69 | } 70 | GUI.enabled = true; 71 | } 72 | 73 | void OnDestroy() 74 | { 75 | GameObject.DestroyImmediate(exporterGo); 76 | exporter = null; 77 | } 78 | 79 | void ExportFile() // Create (Export) button has been hit (NOT wizard has been created!) 80 | { 81 | var ext = GlTF_Writer.binary ? "glb" : "gltf"; 82 | path = EditorUtility.SaveFilePanel("Save glTF file as", savedPath, savedFile, ext); 83 | if (path.Length != 0) 84 | { 85 | if (presetAsset != null) 86 | { 87 | string psPath = AssetDatabase.GetAssetPath(presetAsset); 88 | if (psPath != null) 89 | { 90 | psPath = psPath.Remove(0, "Assets".Length); 91 | psPath = Application.dataPath + psPath; 92 | preset.Load(psPath); 93 | } 94 | } 95 | exporter.ExportCoroutine(path, preset, buildZip, true, exportAnimation, convertImages); 96 | } 97 | } 98 | } 99 | #endif -------------------------------------------------------------------------------- /GlTF_FloatArray.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_FloatArray : GlTF_Writer { 6 | public float[] items; 7 | public int minItems = 0; 8 | public int maxItems = 0; 9 | 10 | public GlTF_FloatArray () { } 11 | public GlTF_FloatArray (string n) { name = n; } 12 | 13 | public override void Write() 14 | { 15 | if (name.Length > 0) 16 | { 17 | Indent(); jsonWriter.Write ("\"" + name + "\": ["); 18 | } 19 | WriteVals(); 20 | if (name.Length > 0) 21 | { 22 | jsonWriter.Write ("]"); 23 | } 24 | } 25 | 26 | public virtual void WriteVals () 27 | { 28 | for (int i = 0; i < maxItems; i++) 29 | { 30 | if (i > 0) 31 | jsonWriter.Write (", "); 32 | jsonWriter.Write (items[i].ToString ()); 33 | } 34 | } 35 | } 36 | #endif -------------------------------------------------------------------------------- /GlTF_FloatArray4.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_FloatArray4 : GlTF_FloatArray { 6 | public GlTF_FloatArray4() { minItems = 4; maxItems = 4; items = new float[] { 1.0f, 0.0f, 0.0f, 0.0f }; } 7 | /* 8 | public override void Write() 9 | { 10 | Indent(); jsonWriter.Write ("\"rotation\": [ "); 11 | WriteVals(); 12 | jsonWriter.Write ("]"); 13 | } 14 | */ 15 | } 16 | #endif -------------------------------------------------------------------------------- /GlTF_Image.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Image : GlTF_Writer { 6 | public string uri; 7 | 8 | public static string GetNameFromObject(Object o) 9 | { 10 | return "image_" + GlTF_Writer.GetNameFromObject(o, true); 11 | } 12 | 13 | public override void Write() 14 | { 15 | Indent(); jsonWriter.Write ("{\n"); 16 | IndentIn(); 17 | Indent(); jsonWriter.Write ("\"uri\": \"" + uri + "\"\n"); 18 | IndentOut(); 19 | Indent(); jsonWriter.Write ("}"); 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /GlTF_Light.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Light : GlTF_Writer { 6 | public GlTF_ColorRGB color; 7 | public string type; 8 | // public override void Write () 9 | // { 10 | // } 11 | } 12 | #endif -------------------------------------------------------------------------------- /GlTF_Material.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class GlTF_Material : GlTF_Writer { 7 | 8 | public class Value : GlTF_Writer { 9 | } 10 | 11 | public class ColorValue : Value { 12 | public Color color; 13 | public bool isRGB = false; 14 | 15 | public override void Write() 16 | { 17 | jsonWriter.Write ("\"" + name + "\": ["); 18 | jsonWriter.Write (color.r.ToString() + ", " + color.g.ToString() + ", " +color.b.ToString() + (isRGB ? "" : ", " + color.a.ToString())); 19 | jsonWriter.Write ("]"); 20 | } 21 | } 22 | 23 | public class VectorValue : Value { 24 | public Vector4 vector; 25 | 26 | public override void Write() 27 | { 28 | jsonWriter.Write ("\"" + name + "\": ["); 29 | jsonWriter.Write (vector.x.ToString() + ", " + vector.y.ToString() + ", " + vector.z.ToString() + ", " + vector.w.ToString()); 30 | jsonWriter.Write ("]"); 31 | } 32 | } 33 | 34 | public class FloatValue : Value { 35 | public float value; 36 | 37 | public override void Write() 38 | { 39 | jsonWriter.Write("\"" + name + "\": " + value); 40 | } 41 | } 42 | 43 | public class IntValue : Value 44 | { 45 | public int value; 46 | 47 | public override void Write() 48 | { 49 | jsonWriter.Write("\"" + name + "\": " + value); 50 | } 51 | } 52 | 53 | public class BoolValue : Value 54 | { 55 | public bool value; 56 | 57 | public override void Write() 58 | { 59 | jsonWriter.Write("\"" + name + "\": " + (value ? "true" : "false")); 60 | } 61 | } 62 | 63 | public class StringValue : Value { 64 | public string value; 65 | 66 | public override void Write() 67 | { 68 | jsonWriter.Write ("\"" + name + "\": \"" + value +"\""); 69 | } 70 | } 71 | 72 | public class DictValue: Value 73 | { 74 | public Dictionary intValue; 75 | public Dictionary floatValue; 76 | public Dictionary stringValue; 77 | public DictValue() 78 | { 79 | intValue = new Dictionary(); 80 | floatValue = new Dictionary(); 81 | stringValue = new Dictionary(); 82 | } 83 | public override void Write() 84 | { 85 | jsonWriter.Write("\"" + name + "\" : {\n"); 86 | IndentIn(); 87 | 88 | foreach (string key in intValue.Keys) 89 | { 90 | CommaNL(); 91 | Indent(); jsonWriter.Write("\"" + key + "\" : " + intValue[key]); 92 | } 93 | foreach (string key in floatValue.Keys) 94 | { 95 | CommaNL(); 96 | Indent(); jsonWriter.Write("\"" + key + "\" : " + floatValue[key]); 97 | } 98 | foreach (string key in stringValue.Keys) 99 | { 100 | CommaNL(); 101 | Indent(); jsonWriter.Write("\"" + key + "\" : " + stringValue[key]); 102 | } 103 | jsonWriter.Write("\n"); 104 | IndentOut(); 105 | Indent(); jsonWriter.Write("}"); 106 | } 107 | } 108 | 109 | public int instanceTechniqueIndex; 110 | public bool isMetal = false; 111 | public float shininess; 112 | public List values = new List(); 113 | public List pbrValues = new List(); 114 | 115 | public static string GetNameFromObject(Object o) 116 | { 117 | return "material_" + GlTF_Writer.GetNameFromObject(o, true); 118 | } 119 | 120 | public override void Write() 121 | { 122 | Indent(); jsonWriter.Write("{\n"); 123 | IndentIn(); 124 | writeExtras(); 125 | if (isMetal) 126 | { 127 | Indent(); jsonWriter.Write("\"pbrMetallicRoughness\": {\n"); 128 | } 129 | else 130 | { 131 | Indent(); jsonWriter.Write("\"extensions\": {\n"); 132 | IndentIn(); 133 | 134 | Indent(); jsonWriter.Write("\"KHR_materials_pbrSpecularGlossiness\": {\n"); 135 | } 136 | IndentIn(); 137 | foreach (var v in pbrValues) 138 | { 139 | CommaNL(); 140 | Indent(); v.Write(); 141 | } 142 | if (!isMetal) 143 | { 144 | IndentOut(); 145 | Indent(); jsonWriter.Write("}"); 146 | jsonWriter.Write("\n"); 147 | } 148 | 149 | jsonWriter.Write("\n"); 150 | IndentOut(); 151 | Indent(); jsonWriter.Write("},\n"); 152 | 153 | // write common values 154 | foreach (var v in values) 155 | { 156 | CommaNL(); 157 | Indent(); v.Write(); 158 | } 159 | CommaNL(); 160 | Indent(); jsonWriter.Write ("\"name\": \"" + name + "\"\n"); 161 | IndentOut(); 162 | Indent(); jsonWriter.Write ("}"); 163 | 164 | } 165 | 166 | } 167 | #endif -------------------------------------------------------------------------------- /GlTF_MaterialColor.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_MaterialColor : GlTF_ColorOrTexture { 6 | public GlTF_MaterialColor (string n, Color c) { name = n; color = new GlTF_ColorRGBA(name,c); } 7 | public GlTF_ColorRGBA color = new GlTF_ColorRGBA ("diffuse"); 8 | public override void Write() 9 | { 10 | // Indent(); jsonWriter.Write ("\"" + name + "\": "); 11 | color.Write (); 12 | } 13 | } 14 | #endif -------------------------------------------------------------------------------- /GlTF_MaterialTexture.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_MaterialTexture : GlTF_ColorOrTexture { 6 | public GlTF_MaterialTexture (string n, GlTF_Texture t) { name = n; texture = t; } 7 | public GlTF_Texture texture; 8 | public override void Write() 9 | { 10 | Indent(); jsonWriter.Write ("\"" + name + "\": \""+texture.name+"\""); 11 | } 12 | } 13 | #endif -------------------------------------------------------------------------------- /GlTF_Matrix.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Matrix : GlTF_FloatArray { 6 | public GlTF_Matrix() { name = "matrix"; minItems = 16; maxItems = 16; items = new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; } 7 | public GlTF_Matrix(Matrix4x4 m, bool convertLeftRight=true) 8 | { 9 | name = "matrix"; 10 | minItems = 16; 11 | maxItems = 16; 12 | 13 | // unity: m[row][col] 14 | // gltf: column major 15 | if(convertRightHanded && convertLeftRight) 16 | convertMatrixLeftToRightHandedness(ref m); 17 | 18 | items = new float[] { 19 | m.m00, m.m10, m.m20, m.m30, 20 | m.m01, m.m11, m.m21, m.m31, 21 | m.m02, m.m12, m.m22, m.m32, 22 | m.m03, m.m13, m.m23, m.m33 23 | }; 24 | } 25 | } 26 | #endif -------------------------------------------------------------------------------- /GlTF_Mesh.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class GlTF_Mesh : GlTF_Writer { 7 | public List primitives; 8 | 9 | public GlTF_Mesh() { primitives = new List(); } 10 | 11 | public static string GetNameFromObject(Object o) 12 | { 13 | return "mesh_" + GlTF_Writer.GetNameFromObject(o, true); 14 | } 15 | 16 | public void Populate (Mesh m) 17 | { 18 | if (primitives.Count > 0) 19 | { 20 | // only populate first attributes because the data are shared between primitives 21 | primitives[0].attributes.Populate(m); 22 | } 23 | 24 | foreach (GlTF_Primitive p in primitives) 25 | { 26 | p.Populate (m); 27 | } 28 | } 29 | 30 | public override void Write () 31 | { 32 | Indent(); jsonWriter.Write ("{\n"); 33 | IndentIn(); 34 | Indent(); jsonWriter.Write ("\"name\": \"" + name + "\",\n"); 35 | Indent(); jsonWriter.Write ("\"primitives\": [\n"); 36 | IndentIn(); 37 | foreach (GlTF_Primitive p in primitives) 38 | { 39 | CommaNL(); 40 | Indent(); jsonWriter.Write ("{\n"); 41 | p.Write (); 42 | Indent(); jsonWriter.Write ("}"); 43 | } 44 | jsonWriter.WriteLine(); 45 | IndentOut(); 46 | Indent(); jsonWriter.Write ("]\n"); 47 | IndentOut(); 48 | Indent(); jsonWriter.Write ("}"); 49 | } 50 | } 51 | #endif -------------------------------------------------------------------------------- /GlTF_Node.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class GlTF_Node : GlTF_Writer { 7 | public string cameraName; 8 | public bool hasParent = false; 9 | public List childrenNames = new List(); 10 | public bool uniqueItems = true; 11 | public string lightName; 12 | public ListbufferViewNames = new List(); 13 | public ListindexNames = new List(); 14 | public ListaccessorNames = new List(); 15 | public int meshIndex = -1; 16 | public GlTF_Matrix matrix; 17 | // public GlTF_Mesh mesh; 18 | public GlTF_Rotation rotation; 19 | public GlTF_Scale scale; 20 | public GlTF_Translation translation; 21 | public int skinIndex = -1; 22 | public List skeletons = new List(); 23 | public bool additionalProperties = false; 24 | 25 | public static string GetNameFromObject(Object o) 26 | { 27 | return "node_" + GlTF_Writer.GetNameFromObject(o, true); 28 | } 29 | 30 | public override void Write () 31 | { 32 | Indent(); 33 | jsonWriter.Write ("{\n"); 34 | IndentIn(); 35 | Indent(); 36 | CommaNL(); 37 | jsonWriter.Write ("\"name\": \"" + id + "\""); 38 | if (cameraName != null) 39 | { 40 | CommaNL(); 41 | Indent(); 42 | jsonWriter.Write ("\"camera\": \""+cameraName+"\""); 43 | } 44 | else if (lightName != null) 45 | { 46 | CommaNL(); 47 | Indent(); 48 | jsonWriter.Write ("\"light\": \""+lightName+"\""); 49 | } 50 | else if (meshIndex != -1) 51 | { 52 | CommaNL(); 53 | Indent(); 54 | jsonWriter.Write ("\"mesh\": " + meshIndex); 55 | } 56 | 57 | if (childrenNames != null && childrenNames.Count > 0) 58 | { 59 | CommaNL(); 60 | Indent(); jsonWriter.Write ("\"children\": [\n"); 61 | IndentIn(); 62 | foreach (string ch in childrenNames) 63 | { 64 | CommaNL(); 65 | Indent(); jsonWriter.Write (GlTF_Writer.nodeNames.IndexOf(ch)); 66 | } 67 | jsonWriter.WriteLine(); 68 | IndentOut(); 69 | Indent(); jsonWriter.Write ("]"); 70 | } 71 | 72 | if (matrix != null) 73 | { 74 | CommaNL(); 75 | matrix.Write(); 76 | } 77 | else 78 | { 79 | if (translation != null && (translation.items[0] != 0f || translation.items[1] != 0f || translation.items[2] != 0f)) 80 | { 81 | CommaNL(); 82 | translation.Write(); 83 | } 84 | if (scale != null && (scale.items[0] != 1f || scale.items[1] != 1f || scale.items[2] != 1f)) 85 | { 86 | CommaNL(); 87 | scale.Write(); 88 | } 89 | if (rotation != null && (rotation.items[0] != 0f || rotation.items[1] != 0f || rotation.items[2] != 0f || rotation.items[3] != 0f)) 90 | { 91 | CommaNL(); 92 | rotation.Write(); 93 | } 94 | } 95 | jsonWriter.Write("\n"); 96 | 97 | if (skinIndex > -1) 98 | { 99 | CommaNL(); 100 | Indent(); jsonWriter.Write("\"skin\": " + skinIndex + "\n"); 101 | } 102 | 103 | IndentOut(); 104 | Indent(); jsonWriter.Write ("}"); 105 | } 106 | } 107 | #endif -------------------------------------------------------------------------------- /GlTF_Orthographic.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Orthographic : GlTF_Camera { 6 | public float xmag; 7 | public float ymag; 8 | public float zfar; 9 | public float znear; 10 | public GlTF_Orthographic() { type = "orthographic"; } 11 | public override void Write () 12 | { 13 | } 14 | } 15 | #endif -------------------------------------------------------------------------------- /GlTF_Perspective.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Perspective : GlTF_Camera { 6 | public float aspect_ratio; 7 | public float yfov;//": 37.8492, 8 | public float zfar;//": 100, 9 | public float znear;//": 0.01 10 | public GlTF_Perspective() { type = "perspective"; } 11 | public override void Write () 12 | { 13 | /* 14 | "camera_0": { 15 | "perspective": { 16 | "yfov": 45, 17 | "zfar": 3162.76, 18 | "znear": 12.651 19 | }, 20 | "type": "perspective" 21 | } 22 | */ 23 | Indent(); jsonWriter.Write ("{\n"); 24 | IndentIn(); 25 | Indent(); jsonWriter.Write ("\"perspective\": {\n"); 26 | IndentIn(); 27 | Indent(); jsonWriter.Write ("\"aspect_ratio\": "+aspect_ratio.ToString()+",\n"); 28 | Indent(); jsonWriter.Write ("\"yfov\": "+yfov.ToString()+",\n"); 29 | Indent(); jsonWriter.Write ("\"zfar\": "+zfar.ToString()+",\n"); 30 | Indent(); jsonWriter.Write ("\"znear\": "+znear.ToString()+",\n"); 31 | Indent(); jsonWriter.Write("\"name\": \"" + name + "\"\n"); 32 | IndentOut(); 33 | Indent(); jsonWriter.Write ("},\n"); 34 | Indent(); jsonWriter.Write ("\"type\": \"perspective\"\n"); 35 | IndentOut(); 36 | Indent(); jsonWriter.Write ("}"); 37 | } 38 | } 39 | #endif -------------------------------------------------------------------------------- /GlTF_PointLight.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_PointLight : GlTF_Light { 6 | public float constantAttenuation = 1f; 7 | public float linearAttenuation = 0f; 8 | public float quadraticAttenuation = 0f; 9 | 10 | public GlTF_PointLight () { type = "point"; } 11 | 12 | public override void Write() 13 | { 14 | color.Write(); 15 | Indent(); jsonWriter.Write ("\"constantAttentuation\": "+constantAttenuation); 16 | Indent(); jsonWriter.Write ("\"linearAttenuation\": "+linearAttenuation); 17 | Indent(); jsonWriter.Write ("\"quadraticAttenuation\": "+quadraticAttenuation); 18 | jsonWriter.Write ("}"); 19 | } 20 | } 21 | #endif -------------------------------------------------------------------------------- /GlTF_Primitive.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Primitive : GlTF_Writer { 6 | public GlTF_Attributes attributes = new GlTF_Attributes(); 7 | public GlTF_Accessor indices; 8 | public int materialIndex; 9 | public int primitive = 4; 10 | public int semantics = 4; 11 | public int index = 0; 12 | 13 | public static string GetNameFromObject(Object o, int index) 14 | { 15 | return "primitive_" + index + "_" + GlTF_Writer.GetNameFromObject(o, true); 16 | } 17 | 18 | public void Populate (Mesh m) 19 | { 20 | // attributes.Populate (m); 21 | indices.Populate (m.GetTriangles(index), true); 22 | } 23 | 24 | public override void Write () 25 | { 26 | IndentIn(); 27 | CommaNL(); 28 | if (attributes != null) 29 | attributes.Write(); 30 | CommaNL(); 31 | Indent(); jsonWriter.Write ("\"indices\": " + GlTF_Writer.accessors.IndexOf(indices) + ",\n"); 32 | Indent(); jsonWriter.Write ("\"material\": " + materialIndex + ",\n"); 33 | Indent(); jsonWriter.Write ("\"mode\": " + primitive + "\n"); 34 | // semantics 35 | IndentOut(); 36 | } 37 | } 38 | #endif -------------------------------------------------------------------------------- /GlTF_Program.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class GlTF_Program : GlTF_Writer { 7 | public List attributes = new List(); 8 | public string vertexShader = ""; 9 | public string fragmentShader = ""; 10 | 11 | public static string GetNameFromObject(Object o) 12 | { 13 | return "program_" + GlTF_Writer.GetNameFromObject(o); 14 | } 15 | 16 | public override void Write() 17 | { 18 | Indent(); jsonWriter.Write ("{\n"); 19 | IndentIn(); 20 | Indent(); jsonWriter.Write ("\"attributes\": [\n"); 21 | IndentIn(); 22 | foreach (var a in attributes) 23 | { 24 | CommaNL(); 25 | Indent(); jsonWriter.Write ("\"" + a + "\""); 26 | } 27 | jsonWriter.Write ("\n"); 28 | IndentOut(); 29 | Indent(); jsonWriter.Write ("],\n"); 30 | Indent(); jsonWriter.Write ("\"vertexShader\": \"" + vertexShader + "\",\n"); 31 | Indent(); jsonWriter.Write ("\"fragmentShader\": \"" + fragmentShader + "\"\n"); 32 | IndentOut(); 33 | Indent(); jsonWriter.Write ("}"); 34 | } 35 | } 36 | #endif -------------------------------------------------------------------------------- /GlTF_Rotation.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Rotation : GlTF_FloatArray4 { 6 | public GlTF_Rotation(Quaternion q) 7 | { 8 | if (convertRightHanded) 9 | convertQuatLeftToRightHandedness(ref q); 10 | 11 | name = "rotation"; 12 | minItems = 4; 13 | maxItems = 4; 14 | items = new float[] { q.x, q.y, q.z, q.w }; 15 | } 16 | /* 17 | public override void Write() 18 | { 19 | Indent(); jsonWriter.Write ("\"rotation\": [ "); 20 | WriteVals(); 21 | jsonWriter.Write ("]"); 22 | } 23 | */ 24 | } 25 | #endif -------------------------------------------------------------------------------- /GlTF_Sampler.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Sampler : GlTF_Writer { 6 | public enum MagFilter { 7 | NEAREST = 9728, 8 | LINEAR = 9729 9 | } 10 | 11 | public enum MinFilter { 12 | NEAREST = 9728, 13 | LINEAR = 9729, 14 | NEAREST_MIPMAP_NEAREST = 9984, 15 | LINEAR_MIPMAP_NEAREST = 9985, 16 | NEAREST_MIPMAP_LINEAR = 9986, 17 | LINEAR_MIPMAP_LINEAR = 9987 18 | } 19 | 20 | public enum Wrap { 21 | CLAMP_TO_EDGE = 33071, 22 | MIRRORED_REPEAT = 33648, 23 | REPEAT = 10497 24 | } 25 | 26 | MagFilter magFilter = MagFilter.LINEAR; 27 | MinFilter minFilter = MinFilter.LINEAR; 28 | Wrap wrap = Wrap.REPEAT; 29 | 30 | public static string GetNameFromObject(Texture tex) 31 | { 32 | int fm = (int)tex.filterMode; 33 | int w = (int)tex.wrapMode; 34 | var n = "sampler_" + fm + "_" + w; 35 | Texture2D t = tex as Texture2D; 36 | if (t != null) 37 | { 38 | if (t.mipmapCount > 0) 39 | { 40 | n += "_m"; 41 | } 42 | } 43 | return n; 44 | } 45 | 46 | public GlTF_Sampler (Texture tex) 47 | { 48 | bool hasMipMap = false; 49 | Texture2D t = tex as Texture2D; 50 | if (t != null) 51 | { 52 | if (t.mipmapCount > 0) 53 | { 54 | hasMipMap = true; 55 | } 56 | } 57 | 58 | switch (tex.filterMode) 59 | { 60 | case FilterMode.Point: 61 | { 62 | magFilter = MagFilter.NEAREST; 63 | if (hasMipMap) 64 | { 65 | minFilter = MinFilter.NEAREST_MIPMAP_NEAREST; 66 | } 67 | else 68 | { 69 | minFilter = MinFilter.NEAREST; 70 | } 71 | } 72 | break; 73 | 74 | case FilterMode.Bilinear: 75 | { 76 | magFilter = MagFilter.LINEAR; 77 | if (hasMipMap) 78 | { 79 | minFilter = MinFilter.LINEAR_MIPMAP_NEAREST; 80 | } 81 | else 82 | { 83 | minFilter = MinFilter.LINEAR; 84 | } 85 | } 86 | break; 87 | 88 | case FilterMode.Trilinear: 89 | { 90 | magFilter = MagFilter.LINEAR; 91 | if (hasMipMap) 92 | { 93 | minFilter = MinFilter.LINEAR; 94 | } 95 | else 96 | { 97 | minFilter = MinFilter.LINEAR_MIPMAP_LINEAR; 98 | } 99 | } 100 | break; 101 | } 102 | 103 | switch (tex.wrapMode) 104 | { 105 | case TextureWrapMode.Clamp: 106 | { 107 | wrap = Wrap.CLAMP_TO_EDGE; 108 | } 109 | break; 110 | 111 | case TextureWrapMode.Repeat: 112 | { 113 | wrap = Wrap.REPEAT; 114 | } 115 | break; 116 | } 117 | } 118 | 119 | public override void Write() 120 | { 121 | Indent(); jsonWriter.Write ("{\n"); 122 | IndentIn(); 123 | Indent(); jsonWriter.Write ("\"magFilter\": " + (int)magFilter + ",\n"); 124 | Indent(); jsonWriter.Write ("\"minFilter\": " + (int)minFilter + ",\n"); 125 | Indent(); jsonWriter.Write ("\"wrapS\": " + (int)wrap + ",\n"); 126 | Indent(); jsonWriter.Write ("\"wrapT\": " + (int)wrap + "\n"); 127 | IndentOut(); 128 | Indent(); jsonWriter.Write ("}"); 129 | } 130 | } 131 | #endif -------------------------------------------------------------------------------- /GlTF_Scale.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Scale : GlTF_Vector3 { 6 | public GlTF_Scale() { items = new float[] {1f, 1f, 1f}; } 7 | public GlTF_Scale(Vector3 v) { items = new float[] {v.x, v.y, v.z }; } 8 | public override void Write() 9 | { 10 | Indent(); jsonWriter.Write ("\"scale\": [ "); 11 | WriteVals(); 12 | jsonWriter.Write ("]"); 13 | } 14 | } 15 | #endif -------------------------------------------------------------------------------- /GlTF_Shader.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Shader : GlTF_Writer { 6 | public enum Type { 7 | Vertex, 8 | Fragment 9 | } 10 | 11 | public Type type = Type.Vertex; 12 | public string uri = ""; 13 | 14 | public static string GetNameFromObject(Object o, Type type) 15 | { 16 | var name = GlTF_Writer.GetNameFromObject(o); 17 | var typeName = type == Type.Vertex ? "vertex" : "fragment"; 18 | return typeName + "_" + name; 19 | } 20 | 21 | public override void Write() 22 | { 23 | Indent(); jsonWriter.Write ("\"" + name + "\": {\n"); 24 | IndentIn(); 25 | Indent(); jsonWriter.Write ("\"type\": " + TypeStr() +",\n"); 26 | Indent(); jsonWriter.Write ("\"uri\": \"" + uri +"\"\n"); 27 | IndentOut(); 28 | Indent(); jsonWriter.Write ("}"); 29 | } 30 | 31 | int TypeStr() 32 | { 33 | if (type == Type.Vertex) 34 | { 35 | return 35633; 36 | } 37 | else if (type == Type.Fragment) 38 | { 39 | return 35632; 40 | } 41 | 42 | return 0; 43 | } 44 | } 45 | #endif -------------------------------------------------------------------------------- /GlTF_Skin.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class GlTF_Skin : GlTF_Writer { 7 | public int invBindMatricesAccessorIndex; 8 | public List joints; 9 | public Transform mesh; 10 | 11 | public GlTF_Skin() { } 12 | 13 | public static string GetNameFromObject(Object o) 14 | { 15 | return "skin_" + GlTF_Writer.GetNameFromObject(o, true); 16 | } 17 | 18 | public void Populate (Transform m, ref GlTF_Accessor invBindMatricesAccessor, int invBindAccessorIndex) 19 | { 20 | SkinnedMeshRenderer skinMesh = m.GetComponent(); 21 | if (!skinMesh) 22 | return; 23 | 24 | // Populate bind poses. From https://docs.unity3d.com/ScriptReference/Mesh-bindposes.html: 25 | // The bind pose is bone's inverse transformation matrix 26 | // In this case we also make this matrix relative to the root 27 | // So that we can move the root game object around freely 28 | 29 | joints = new List(); 30 | //Collect all bones from skin object. Order should be kept here since bones are referenced in the mesh 31 | foreach(Transform t in skinMesh.bones) 32 | { 33 | joints.Add(t); 34 | } 35 | 36 | Matrix4x4[] invBindMatrices = new Matrix4x4[joints.Count]; 37 | for (int i = 0; i < skinMesh.bones.Length; ++i) 38 | { 39 | // Generates inverseWorldMatrix in right-handed coordinate system 40 | Matrix4x4 invBind = skinMesh.sharedMesh.bindposes[i]; 41 | convertMatrixLeftToRightHandedness(ref invBind); 42 | invBindMatrices[i] = invBind; 43 | } 44 | 45 | invBindMatricesAccessor.Populate(invBindMatrices, m); 46 | invBindMatricesAccessorIndex = invBindAccessorIndex; 47 | } 48 | 49 | public override void Write () 50 | { 51 | Indent(); jsonWriter.Write ("{\n"); 52 | IndentIn(); 53 | 54 | Indent(); jsonWriter.Write("\"inverseBindMatrices\": "+ invBindMatricesAccessorIndex + ",\n"); 55 | Indent(); jsonWriter.Write ("\"joints\": [\n"); 56 | 57 | IndentIn(); 58 | foreach (Transform j in joints) 59 | { 60 | CommaNL(); 61 | Indent(); jsonWriter.Write ("" + GlTF_Writer.nodeNames.IndexOf(GlTF_Node.GetNameFromObject(j))); 62 | } 63 | 64 | IndentOut(); 65 | jsonWriter.WriteLine(); 66 | Indent(); jsonWriter.Write ("],\n"); 67 | 68 | Indent(); jsonWriter.Write("\"name\": \"" + name + "\"\n"); 69 | 70 | IndentOut(); 71 | Indent(); jsonWriter.Write ("}"); 72 | } 73 | } 74 | #endif -------------------------------------------------------------------------------- /GlTF_SpotLight.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_SpotLight : GlTF_Light { 6 | public float constantAttenuation = 1f; 7 | public float fallOffAngle = 3.1415927f; 8 | public float fallOffExponent = 0f; 9 | public float linearAttenuation = 0f; 10 | public float quadraticAttenuation = 0f; 11 | 12 | public GlTF_SpotLight () { type = "spot"; } 13 | 14 | public override void Write() 15 | { 16 | color.Write(); 17 | Indent(); jsonWriter.Write ("\"constantAttentuation\": "+constantAttenuation); 18 | Indent(); jsonWriter.Write ("\"fallOffAngle\": "+fallOffAngle); 19 | Indent(); jsonWriter.Write ("\"fallOffExponent\": "+fallOffExponent); 20 | Indent(); jsonWriter.Write ("\"linearAttenuation\": "+linearAttenuation); 21 | Indent(); jsonWriter.Write ("\"quadraticAttenuation\": "+quadraticAttenuation); 22 | jsonWriter.Write ("}"); 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /GlTF_Target.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Target : GlTF_Writer { 6 | public string path; 7 | public override void Write() 8 | { 9 | Indent(); jsonWriter.Write ("\"" + "target" + "\": {\n"); 10 | IndentIn(); 11 | Indent(); jsonWriter.Write ("\"node\": " + GlTF_Writer.nodeNames.IndexOf(id) + ",\n"); 12 | Indent(); jsonWriter.Write ("\"path\": \"" + path + "\"\n"); 13 | IndentOut(); 14 | Indent(); jsonWriter.Write ("}"); 15 | } 16 | } 17 | #endif -------------------------------------------------------------------------------- /GlTF_Technique.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class GlTF_Technique : GlTF_Writer { 7 | public enum Type { 8 | FLOAT = 5126, 9 | FLOAT_VEC2 = 35664, 10 | FLOAT_VEC3 = 35665, 11 | FLOAT_VEC4 = 35666, 12 | FLOAT_MAT3 = 35675, 13 | FLOAT_MAT4 = 35676, 14 | SAMPLER_2D = 35678 15 | } 16 | 17 | public enum Semantic { 18 | UNKNOWN, 19 | POSITION, 20 | NORMAL, 21 | TEXCOORD_0, 22 | TEXCOORD_1, 23 | TEXCOORD_2, 24 | TEXCOORD_3, 25 | MODELVIEW, 26 | PROJECTION, 27 | MODELVIEWINVERSETRANSPOSE 28 | } 29 | 30 | public class Parameter { 31 | public string name; 32 | public Type type; 33 | public Semantic semantic = Semantic.UNKNOWN; 34 | } 35 | 36 | public class Attribute { 37 | public string name; 38 | public string param; 39 | } 40 | 41 | public class Uniform { 42 | public string name; 43 | public string param; 44 | } 45 | 46 | public string program; 47 | public List attributes = new List(); 48 | public List parameters = new List(); 49 | public List uniforms = new List(); 50 | 51 | public static string GetNameFromObject(Object o) 52 | { 53 | return "technique_" + GlTF_Writer.GetNameFromObject(o); 54 | } 55 | 56 | public void AddDefaultUniforms() 57 | { 58 | var tParam = new Parameter(); 59 | tParam.name = "modelViewMatrix"; 60 | tParam.type = Type.FLOAT_MAT4; 61 | tParam.semantic = Semantic.MODELVIEW; 62 | parameters.Add(tParam); 63 | var uni = new Uniform(); 64 | uni.name = "u_modelViewMatrix"; 65 | uni.param = tParam.name; 66 | uniforms.Add(uni); 67 | 68 | tParam = new Parameter(); 69 | tParam.name = "projectionMatrix"; 70 | tParam.type = Type.FLOAT_MAT4; 71 | tParam.semantic = Semantic.PROJECTION; 72 | parameters.Add(tParam); 73 | uni = new Uniform(); 74 | uni.name = "u_projectionMatrix"; 75 | uni.param = tParam.name; 76 | uniforms.Add(uni); 77 | 78 | tParam = new Parameter(); 79 | tParam.name = "normalMatrix"; 80 | tParam.type = Type.FLOAT_MAT3; 81 | tParam.semantic = Semantic.MODELVIEWINVERSETRANSPOSE; 82 | parameters.Add(tParam); 83 | uni = new Uniform(); 84 | uni.name = "u_normalMatrix"; 85 | uni.param = tParam.name; 86 | uniforms.Add(uni); 87 | } 88 | 89 | public override void Write() 90 | { 91 | Indent(); jsonWriter.Write ("\"" + name + "\": {\n"); 92 | IndentIn(); 93 | Indent(); jsonWriter.Write ("\"program\": \"" + program +"\",\n"); 94 | Indent(); jsonWriter.Write ("\"parameters\": {\n"); 95 | IndentIn(); 96 | foreach (var p in parameters) 97 | { 98 | CommaNL(); 99 | Indent(); jsonWriter.Write ("\"" + p.name + "\": {\n"); 100 | IndentIn(); 101 | Indent(); jsonWriter.Write ("\"type\": " + (int)p.type); 102 | if (p.semantic != Semantic.UNKNOWN) 103 | { 104 | jsonWriter.Write (",\n"); 105 | Indent(); jsonWriter.Write ("\"semantic\": \"" + p.semantic + "\"\n"); 106 | } else { 107 | jsonWriter.Write ("\n"); 108 | } 109 | IndentOut(); 110 | Indent(); jsonWriter.Write ("}"); 111 | } 112 | jsonWriter.Write ("\n"); 113 | IndentOut(); 114 | Indent(); jsonWriter.Write ("},\n"); 115 | 116 | Indent(); jsonWriter.Write ("\"attributes\": {\n"); 117 | IndentIn(); 118 | foreach (var a in attributes) 119 | { 120 | CommaNL(); 121 | Indent(); jsonWriter.Write ("\"" + a.name + "\": \"" + a.param + "\""); 122 | } 123 | jsonWriter.Write ("\n"); 124 | IndentOut(); 125 | Indent(); jsonWriter.Write ("},\n"); 126 | 127 | Indent(); jsonWriter.Write ("\"uniforms\": {\n"); 128 | IndentIn(); 129 | foreach (var u in uniforms) 130 | { 131 | CommaNL(); 132 | Indent(); jsonWriter.Write ("\"" + u.name + "\": \"" + u.param + "\""); 133 | } 134 | jsonWriter.Write ("\n"); 135 | IndentOut(); 136 | Indent(); jsonWriter.Write ("}\n"); 137 | IndentOut(); 138 | Indent(); jsonWriter.Write ("}"); 139 | } 140 | } 141 | #endif -------------------------------------------------------------------------------- /GlTF_Texture.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Texture : GlTF_Writer { 6 | /* 7 | "texture_O21_jpg": { 8 | "format": 6408, 9 | "internalFormat": 6408, 10 | "sampler": "sampler_0", 11 | "source": "O21_jpg", 12 | "target": 3553, 13 | "type": 5121 14 | }, 15 | */ 16 | public int samplerIndex; 17 | public int source; 18 | public bool flipy = true; 19 | 20 | public static string GetNameFromObject(Object o) 21 | { 22 | return "texture_" + GlTF_Writer.GetNameFromObject(o, true); 23 | } 24 | 25 | public override void Write() 26 | { 27 | Indent(); jsonWriter.Write ("{\n"); 28 | IndentIn(); 29 | 30 | writeExtras(); 31 | 32 | Indent(); jsonWriter.Write ("\"sampler\": " + samplerIndex + ",\n"); 33 | Indent(); jsonWriter.Write ("\"source\": " + source + "\n"); 34 | IndentOut(); 35 | Indent(); jsonWriter.Write ("}"); 36 | } 37 | } 38 | #endif -------------------------------------------------------------------------------- /GlTF_Translation.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Translation : GlTF_Vector3 { 6 | public GlTF_Translation (Vector3 v) 7 | { 8 | if (convertRightHanded) 9 | convertVector3LeftToRightHandedness(ref v); 10 | 11 | items = new float[] { v.x, v.y, v.z }; 12 | } 13 | public override void Write() 14 | { 15 | Indent(); jsonWriter.Write ("\"translation\": [ "); 16 | WriteVals(); 17 | jsonWriter.Write ("]"); 18 | } 19 | } 20 | #endif -------------------------------------------------------------------------------- /GlTF_Vector3.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class GlTF_Vector3 : GlTF_FloatArray { 6 | public GlTF_Vector3() { minItems = 3; maxItems = 3; items = new float[] {0f, 0f, 0f}; } 7 | public GlTF_Vector3(Vector3 v) { minItems = 3; maxItems = 3; items = new float[] {v.x, v.y, v.z}; } 8 | } 9 | #endif -------------------------------------------------------------------------------- /GlTF_Writer.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.IO; 5 | using System.Collections.Generic; 6 | using System.Text.RegularExpressions; 7 | 8 | public class GlTF_Writer { 9 | public static FileStream fs; 10 | public static StreamWriter jsonWriter; 11 | public static BinaryWriter binWriter; 12 | public static Stream binFile; 13 | public static int indent = 0; 14 | public static string binFileName; 15 | public static bool binary; 16 | static bool[] firsts = new bool[100]; 17 | public static GlTF_BufferView ushortBufferView = new GlTF_BufferView("ushortBufferView", 0, 34963); 18 | public static GlTF_BufferView floatBufferView = new GlTF_BufferView("floatBufferView", 0); 19 | public static GlTF_BufferView vec2BufferView = new GlTF_BufferView("vec2BufferView", 8); 20 | public static GlTF_BufferView vec3BufferView = new GlTF_BufferView("vec3BufferView", 12); 21 | public static GlTF_BufferView vec4BufferView = new GlTF_BufferView("vec4BufferView", 16); 22 | public static GlTF_BufferView vec4UshortBufferView = new GlTF_BufferView("vec4UshortBufferView", 8); 23 | public static GlTF_BufferView mat4BufferView = new GlTF_BufferView("mat4BufferView", 64); 24 | 25 | public static GlTF_BufferView vec3BufferViewAnim = new GlTF_BufferView("vec3BufferViewAnim", 12); 26 | public static GlTF_BufferView vec4BufferViewAnim = new GlTF_BufferView("vec4BufferViewAnim", 16); 27 | 28 | public static List bufferViews = new List(); 29 | public static List cameras = new List(); 30 | public static List lights = new List(); 31 | public static List meshes = new List(); 32 | public static List accessors = new List(); 33 | 34 | public static List nodeNames = new List(); 35 | public static List nodes = new List(); 36 | 37 | public static List materialNames = new List(); 38 | public static List materials = new List(); 39 | public static List samplerNames = new List(); 40 | public static List samplers = new List(); 41 | 42 | public static List textureNames = new List(); 43 | public static List textures = new List(); 44 | 45 | public static List imageNames = new List(); 46 | public static List images = new List(); 47 | public static List animations = new List(); 48 | 49 | public static List techniqueNames = new List(); 50 | public static List techniques = new List(); 51 | 52 | public static List programs = new List(); 53 | public static List shaders = new List(); 54 | public static List skins = new List(); 55 | public static List rootNodes = new List(); 56 | 57 | // Keys are original file path, values correspond to the directory in the output zip file 58 | public static Dictionary exportedFiles = new Dictionary(); 59 | // Exporter specifics 60 | public static bool bakeAnimation; 61 | public static bool exportPBRMaterials; 62 | public static bool hasSpecularMaterials = false; 63 | public static bool convertRightHanded = true; 64 | public static string exporterVersion = "2.2.1"; 65 | public static Regex rgx = new Regex("[^a-zA-Z0-9 -_.]"); 66 | 67 | static public string cleanNonAlphanumeric(string s) 68 | { 69 | return rgx.Replace(s, ""); 70 | } 71 | static public string GetNameFromObject(Object o, bool useId = false) 72 | { 73 | var ret = cleanNonAlphanumeric(o.name); 74 | if (useId) 75 | { 76 | ret += "_" + o.GetInstanceID(); 77 | } 78 | return ret; 79 | } 80 | 81 | public void convertVector3LeftToRightHandedness(ref Vector3 vect) 82 | { 83 | vect.z = -vect.z; 84 | } 85 | 86 | public void convertVector4LeftToRightHandedness(ref Vector4 vect) 87 | { 88 | vect.z = -vect.z; 89 | vect.w = -vect.w; 90 | } 91 | 92 | public void convertQuatLeftToRightHandedness(ref Quaternion quat) 93 | { 94 | quat.w = -quat.w; 95 | quat.z = -quat.z; 96 | } 97 | 98 | // Decomposes a matrix, converts each component from left to right handed and 99 | // rebuilds a matrix 100 | // FIXME: there is probably a better way to do that. It doesn't work well with non uniform scales 101 | public void convertMatrixLeftToRightHandedness(ref Matrix4x4 mat) 102 | { 103 | Vector3 position = mat.GetColumn(3); 104 | convertVector3LeftToRightHandedness(ref position); 105 | Quaternion rotation = Quaternion.LookRotation(mat.GetColumn(2), mat.GetColumn(1)); 106 | convertQuatLeftToRightHandedness(ref rotation); 107 | 108 | Vector3 scale = new Vector3(mat.GetColumn(0).magnitude, mat.GetColumn(1).magnitude, mat.GetColumn(2).magnitude); 109 | float epsilon = 0.00001f; 110 | 111 | // Some issues can occurs with non uniform scales 112 | if(Mathf.Abs(scale.x - scale.y) > epsilon || Mathf.Abs(scale.y - scale.z) > epsilon || Mathf.Abs(scale.x - scale.z) > epsilon) 113 | { 114 | Debug.LogWarning("A matrix with non uniform scale is being converted from left to right handed system. This code is not working correctly in this case"); 115 | } 116 | 117 | // Handle negative scale component in matrix decomposition 118 | if (Matrix4x4.Determinant(mat) < 0) 119 | { 120 | Quaternion rot = Quaternion.LookRotation(mat.GetColumn(2), mat.GetColumn(1)); 121 | Matrix4x4 corr = Matrix4x4.TRS(mat.GetColumn(3), rot, Vector3.one).inverse; 122 | Matrix4x4 extractedScale = corr * mat; 123 | scale = new Vector3(extractedScale.m00, extractedScale.m11, extractedScale.m22); 124 | } 125 | 126 | // convert transform values from left handed to right handed 127 | mat.SetTRS(position, rotation, scale); 128 | } 129 | 130 | public void Init() 131 | { 132 | firsts = new bool[100]; 133 | ushortBufferView = new GlTF_BufferView("ushortBufferView", 0, 34963); 134 | floatBufferView = new GlTF_BufferView("floatBufferView", 0); 135 | vec2BufferView = new GlTF_BufferView("vec2BufferView", 8); 136 | vec3BufferView = new GlTF_BufferView("vec3BufferView", 12); 137 | vec4BufferView = new GlTF_BufferView("vec4BufferView", 16); 138 | vec4UshortBufferView = new GlTF_BufferView("vec4iBufferView", 8); 139 | mat4BufferView = new GlTF_BufferView("mat4BufferView", 64); 140 | 141 | //Animation 142 | vec3BufferViewAnim = new GlTF_BufferView("vec3BufferViewAnim", 12); 143 | vec4BufferViewAnim = new GlTF_BufferView("vec4BufferViewAnim", 16); 144 | 145 | vec2BufferView.target = (int)GlTF_BufferView.TARGET.ARRAY; 146 | vec3BufferView.target = (int)GlTF_BufferView.TARGET.ARRAY; 147 | vec4BufferView.target = (int)GlTF_BufferView.TARGET.ARRAY; 148 | ushortBufferView.target = (int)GlTF_BufferView.TARGET.ELEMENT; 149 | 150 | 151 | bufferViews = new List(); 152 | cameras = new List(); 153 | lights = new List(); 154 | meshes = new List(); 155 | accessors = new List(); 156 | 157 | nodes = new List(); 158 | nodeNames = new List(); 159 | 160 | materialNames = new List(); 161 | materials = new List(); 162 | 163 | samplerNames = new List(); 164 | samplers = new List(); 165 | 166 | textureNames = new List(); 167 | textures = new List(); 168 | 169 | imageNames = new List(); 170 | images = new List(); 171 | animations = new List(); 172 | 173 | techniqueNames = new List(); 174 | techniques = new List(); 175 | 176 | programs = new List(); 177 | shaders = new List(); 178 | skins = new List(); 179 | rootNodes = new List(); 180 | 181 | bakeAnimation = true; 182 | hasSpecularMaterials = false; 183 | } 184 | 185 | public void Indent() { 186 | for (int i = 0; i < indent; i++) 187 | jsonWriter.Write ("\t"); 188 | } 189 | 190 | public void IndentIn() { 191 | indent++; 192 | firsts[indent] = true; 193 | } 194 | 195 | public void IndentOut() { 196 | indent--; 197 | } 198 | 199 | public void CommaStart() { 200 | firsts[indent] = false; 201 | } 202 | 203 | public void CommaNL() { 204 | if (!firsts[indent]) 205 | jsonWriter.Write (",\n"); 206 | 207 | firsts[indent] = false; 208 | } 209 | 210 | public string id; 211 | public string name; // name of this object 212 | 213 | // Extra data for objects 214 | public Dictionary extraString = new Dictionary(); 215 | public Dictionary extraFloat = new Dictionary(); 216 | public Dictionary extraBool = new Dictionary(); 217 | 218 | public void OpenFiles (string filepath) { 219 | fs = File.Open(filepath, FileMode.Create); 220 | exportedFiles.Add(filepath, ""); // Value is an empty string since we want the file at the root of the .zip file 221 | if (binary) 222 | { 223 | binWriter = new BinaryWriter(fs); 224 | binFile = fs; 225 | fs.Seek(20, SeekOrigin.Begin); // header skip 226 | } 227 | else 228 | { 229 | // separate bin file 230 | binFileName = Path.GetFileNameWithoutExtension(filepath) + ".bin"; 231 | var binPath = Path.Combine(Path.GetDirectoryName(filepath), binFileName); 232 | exportedFiles.Add(binPath, ""); // Value is an empty string since we want the file at the root of the .zip file 233 | binFile = File.Open(binPath, FileMode.Create); 234 | } 235 | 236 | jsonWriter = new StreamWriter (fs); 237 | } 238 | 239 | public void CloseFiles() { 240 | if (binary) 241 | { 242 | binWriter.Close(); 243 | } 244 | else 245 | { 246 | binFile.Close(); 247 | } 248 | 249 | jsonWriter.Close (); 250 | fs.Close(); 251 | } 252 | 253 | public void writeExtras() 254 | { 255 | if (extraFloat.Count > 0 || extraString.Count > 0 || extraBool.Count > 0) 256 | { 257 | Indent(); jsonWriter.Write("\"extras\": {\n"); 258 | IndentIn(); 259 | foreach (var s in extraString) 260 | { 261 | CommaNL(); 262 | Indent(); jsonWriter.Write("\"" + s.Key + "\" : \"" + s.Value + "\""); 263 | } 264 | foreach (var s in extraFloat) 265 | { 266 | CommaNL(); 267 | Indent(); jsonWriter.Write("\"" + s.Key + "\" : " + s.Value + ""); 268 | } 269 | foreach (var s in extraBool) 270 | { 271 | CommaNL(); 272 | Indent(); jsonWriter.Write("\"" + s.Key + "\" : " + (s.Value ? "true" : "false") + ""); 273 | } 274 | IndentOut(); 275 | jsonWriter.Write("\n"); 276 | Indent(); jsonWriter.Write("},"); 277 | jsonWriter.Write("\n"); 278 | } 279 | } 280 | 281 | public virtual void Write () { 282 | 283 | if(ushortBufferView.byteLength > 0) 284 | bufferViews.Add (ushortBufferView); 285 | 286 | if (floatBufferView.byteLength > 0) 287 | bufferViews.Add (floatBufferView); 288 | 289 | if (vec2BufferView.byteLength > 0) 290 | bufferViews.Add (vec2BufferView); 291 | 292 | if (vec3BufferView.byteLength > 0) 293 | bufferViews.Add (vec3BufferView); 294 | 295 | if (vec4BufferView.byteLength > 0) 296 | bufferViews.Add (vec4BufferView); 297 | 298 | if (vec4UshortBufferView.byteLength > 0) 299 | bufferViews.Add(vec4UshortBufferView); 300 | 301 | if (mat4BufferView.byteLength > 0) 302 | bufferViews.Add (mat4BufferView); 303 | 304 | if (vec3BufferViewAnim.byteLength > 0) 305 | bufferViews.Add(vec3BufferViewAnim); 306 | 307 | if (vec4BufferViewAnim.byteLength > 0) 308 | bufferViews.Add(vec4BufferViewAnim); 309 | 310 | ushortBufferView.bin = binary; 311 | floatBufferView.bin = binary; 312 | vec2BufferView.bin = binary; 313 | vec3BufferView.bin = binary; 314 | vec4BufferView.bin = binary; 315 | vec4UshortBufferView.bin = binary; 316 | mat4BufferView.bin = binary; 317 | 318 | vec3BufferViewAnim.bin = binary; 319 | vec4BufferViewAnim.bin = binary; 320 | 321 | // write memory streams to binary file 322 | floatBufferView.byteOffset = 0; 323 | vec2BufferView.byteOffset = floatBufferView.byteOffset + floatBufferView.byteLength; 324 | vec3BufferView.byteOffset = vec2BufferView.byteOffset + vec2BufferView.byteLength; 325 | vec4BufferView.byteOffset = vec3BufferView.byteOffset + vec3BufferView.byteLength; 326 | vec4UshortBufferView.byteOffset = vec4BufferView.byteOffset + vec4BufferView.byteLength; 327 | mat4BufferView.byteOffset = vec4UshortBufferView.byteOffset + vec4UshortBufferView.byteLength; 328 | ushortBufferView.byteOffset = mat4BufferView.byteOffset + mat4BufferView.byteLength; 329 | vec3BufferViewAnim.byteOffset = ushortBufferView.byteOffset + ushortBufferView.byteLength; 330 | vec4BufferViewAnim.byteOffset = vec3BufferViewAnim.byteOffset + vec3BufferViewAnim.byteLength; 331 | 332 | long bufferByteLength = vec4BufferViewAnim.byteOffset + vec4BufferViewAnim.byteLength; 333 | 334 | jsonWriter.Write ("{\n"); 335 | IndentIn(); 336 | 337 | // asset 338 | CommaNL(); 339 | Indent(); jsonWriter.Write ("\"asset\": {\n"); 340 | IndentIn(); 341 | Indent(); jsonWriter.Write ("\"generator\": \"Unity "+ Application.unityVersion + "\",\n"); 342 | 343 | writeExtras(); 344 | 345 | Indent(); jsonWriter.Write ("\"version\": \"2.0\"\n"); 346 | 347 | IndentOut(); 348 | Indent(); jsonWriter.Write ("}"); 349 | 350 | if (accessors != null && accessors.Count > 0) 351 | { 352 | CommaNL(); 353 | Indent(); jsonWriter.Write ("\"accessors\": [\n"); 354 | IndentIn(); 355 | foreach (GlTF_Accessor a in accessors) 356 | { 357 | CommaNL(); 358 | a.Write (); 359 | } 360 | jsonWriter.WriteLine(); 361 | IndentOut(); 362 | Indent(); jsonWriter.Write ("]"); 363 | } 364 | 365 | if (animations.Count > 0) 366 | { 367 | CommaNL(); 368 | Indent(); jsonWriter.Write ("\"animations\": [\n"); 369 | IndentIn(); 370 | foreach (GlTF_Animation a in animations) 371 | { 372 | CommaNL(); 373 | a.Write (); 374 | } 375 | jsonWriter.WriteLine(); 376 | IndentOut(); 377 | Indent(); jsonWriter.Write ("]"); 378 | } 379 | 380 | if (!binary) 381 | { 382 | // FIX: Should support multiple buffers 383 | CommaNL(); 384 | Indent(); jsonWriter.Write ("\"buffers\": [\n"); 385 | IndentIn(); 386 | Indent(); jsonWriter.Write ("{\n"); 387 | IndentIn(); 388 | Indent(); jsonWriter.Write ("\"byteLength\": "+ (bufferByteLength) +",\n"); 389 | Indent(); jsonWriter.Write ("\"uri\": \"" + GlTF_Writer.binFileName + "\"\n"); 390 | 391 | IndentOut(); 392 | Indent(); jsonWriter.Write ("}\n"); 393 | 394 | IndentOut(); 395 | Indent(); jsonWriter.Write ("]"); 396 | } 397 | else 398 | { 399 | CommaNL(); 400 | Indent(); jsonWriter.Write ("\"buffers\": {\n"); 401 | IndentIn(); 402 | Indent(); jsonWriter.Write ("\"binary_glTF\": {\n"); 403 | IndentIn(); 404 | Indent(); jsonWriter.Write ("\"byteLength\": "+ (vec4BufferViewAnim.byteOffset+ vec4BufferViewAnim.byteLength)+",\n"); 405 | Indent(); jsonWriter.Write ("\"type\": \"arraybuffer\"\n"); 406 | 407 | IndentOut(); 408 | Indent(); jsonWriter.Write ("}\n"); 409 | 410 | IndentOut(); 411 | Indent(); jsonWriter.Write ("}"); 412 | } 413 | 414 | if (bufferViews != null && bufferViews.Count > 0) 415 | { 416 | CommaNL(); 417 | Indent(); jsonWriter.Write ("\"bufferViews\": [\n"); 418 | IndentIn(); 419 | foreach (GlTF_BufferView bv in bufferViews) 420 | { 421 | if (bv.byteLength > 0) 422 | { 423 | CommaNL(); 424 | bv.Write (); 425 | } 426 | } 427 | jsonWriter.WriteLine(); 428 | IndentOut(); 429 | Indent(); jsonWriter.Write ("]"); 430 | } 431 | 432 | if (cameras != null && cameras.Count > 0) 433 | { 434 | CommaNL(); 435 | Indent(); jsonWriter.Write ("\"cameras\": [\n"); 436 | IndentIn(); 437 | foreach (GlTF_Camera c in cameras) 438 | { 439 | CommaNL(); 440 | c.Write (); 441 | } 442 | jsonWriter.WriteLine(); 443 | IndentOut(); 444 | Indent(); jsonWriter.Write ("]"); 445 | } 446 | 447 | if(hasSpecularMaterials) 448 | { 449 | CommaNL(); 450 | Indent(); jsonWriter.Write("\"extensionsRequired\": [\n"); 451 | IndentIn(); 452 | Indent(); jsonWriter.Write("\"KHR_materials_pbrSpecularGlossiness\"\n"); 453 | IndentOut(); 454 | Indent(); jsonWriter.Write("]"); 455 | } 456 | 457 | if(hasSpecularMaterials || binary) 458 | { 459 | CommaNL(); 460 | Indent(); jsonWriter.Write("\"extensionsUsed\": [\n"); 461 | IndentIn(); 462 | if (hasSpecularMaterials) 463 | { 464 | Indent(); jsonWriter.Write("\"KHR_materials_pbrSpecularGlossiness\"\n"); 465 | } 466 | if (binary) 467 | { 468 | Indent(); jsonWriter.Write("\"KHR_binary_glTF\"\n"); 469 | } 470 | IndentOut(); 471 | Indent(); jsonWriter.Write("]"); 472 | } 473 | 474 | if (images.Count > 0) 475 | { 476 | CommaNL(); 477 | Indent(); jsonWriter.Write ("\"images\": [\n"); 478 | IndentIn(); 479 | foreach (var i in images) 480 | { 481 | CommaNL(); 482 | i.Write (); 483 | } 484 | jsonWriter.WriteLine(); 485 | IndentOut(); 486 | Indent(); jsonWriter.Write ("]"); 487 | } 488 | 489 | if (materials.Count > 0) 490 | { 491 | CommaNL(); 492 | Indent(); jsonWriter.Write ("\"materials\": [\n"); 493 | IndentIn(); 494 | foreach (GlTF_Material m in materials) 495 | { 496 | CommaNL(); 497 | m.Write (); 498 | } 499 | jsonWriter.WriteLine(); 500 | IndentOut(); 501 | Indent(); jsonWriter.Write ("]"); 502 | } 503 | 504 | if (meshes != null && meshes.Count > 0) 505 | { 506 | CommaNL(); 507 | Indent(); 508 | jsonWriter.Write ("\"meshes\": [\n"); 509 | IndentIn(); 510 | foreach (GlTF_Mesh m in meshes) 511 | { 512 | CommaNL(); 513 | m.Write (); 514 | } 515 | jsonWriter.WriteLine(); 516 | IndentOut(); 517 | Indent(); 518 | jsonWriter.Write ("]"); 519 | } 520 | 521 | if (nodes != null && nodes.Count > 0) 522 | { 523 | CommaNL(); 524 | Indent(); jsonWriter.Write ("\"nodes\": [\n"); 525 | IndentIn(); 526 | foreach (GlTF_Node n in nodes) 527 | { 528 | CommaNL(); 529 | n.Write(); 530 | } 531 | jsonWriter.WriteLine(); 532 | IndentOut(); 533 | Indent(); jsonWriter.Write ("]"); 534 | } 535 | 536 | if (samplers.Count > 0) 537 | { 538 | CommaNL(); 539 | Indent(); jsonWriter.Write ("\"samplers\": [\n"); 540 | IndentIn(); 541 | foreach (GlTF_Sampler s in samplers) 542 | { 543 | CommaNL(); 544 | s.Write (); 545 | } 546 | jsonWriter.WriteLine(); 547 | IndentOut(); 548 | Indent(); jsonWriter.Write ("]"); 549 | } 550 | CommaNL(); 551 | Indent(); jsonWriter.Write ("\"scenes\": [\n"); 552 | IndentIn(); 553 | Indent(); jsonWriter.Write ("{\n"); 554 | IndentIn(); 555 | CommaNL(); 556 | Indent(); jsonWriter.Write("\"name\":\"defaultScene\",\n"); 557 | Indent(); jsonWriter.Write ("\"nodes\": [\n"); 558 | IndentIn(); 559 | foreach (GlTF_Node n in rootNodes) 560 | { 561 | CommaNL(); 562 | Indent(); jsonWriter.Write(nodes.IndexOf(n)); 563 | } 564 | jsonWriter.WriteLine(); 565 | IndentOut(); 566 | Indent(); jsonWriter.Write ("]\n"); 567 | IndentOut(); 568 | Indent(); jsonWriter.Write ("}\n"); 569 | IndentOut(); 570 | Indent(); jsonWriter.Write ("],\n"); 571 | 572 | Indent(); jsonWriter.Write("\"scene\": 0"); 573 | 574 | if(skins.Count > 0) 575 | { 576 | CommaNL(); 577 | Indent(); jsonWriter.Write("\"skins\": [\n"); 578 | IndentIn(); 579 | foreach(GlTF_Skin skin in skins) 580 | { 581 | CommaNL(); 582 | skin.Write(); 583 | } 584 | jsonWriter.WriteLine(); 585 | IndentOut(); 586 | Indent(); jsonWriter.Write("]"); 587 | } 588 | 589 | if (textures.Count > 0) 590 | { 591 | CommaNL(); 592 | Indent(); jsonWriter.Write ("\"textures\": [\n"); 593 | IndentIn(); 594 | foreach (GlTF_Texture t in textures) 595 | { 596 | CommaNL(); 597 | t.Write (); 598 | } 599 | jsonWriter.WriteLine(); 600 | IndentOut(); 601 | Indent(); jsonWriter.Write ("]"); 602 | } 603 | 604 | IndentOut(); 605 | jsonWriter.Write ("\n}"); 606 | jsonWriter.Flush(); 607 | 608 | uint contentLength = 0; 609 | if (binary) 610 | { 611 | long curLen = fs.Position; 612 | var rem = curLen % 4; 613 | if (rem != 0) 614 | { 615 | // add padding if not aligned to 4 bytes 616 | var next = (curLen / 4 + 1) * 4; 617 | rem = next - curLen; 618 | for (int i = 0; i < rem; ++i) 619 | { 620 | jsonWriter.Write(" "); 621 | } 622 | } 623 | jsonWriter.Flush(); 624 | 625 | // current pos - header size 626 | contentLength = (uint)(fs.Position - 20); 627 | } 628 | 629 | floatBufferView.memoryStream.WriteTo(binFile); 630 | vec2BufferView.memoryStream.WriteTo (binFile); 631 | vec3BufferView.memoryStream.WriteTo (binFile); 632 | vec4BufferView.memoryStream.WriteTo (binFile); 633 | vec4UshortBufferView.memoryStream.WriteTo(binFile); 634 | mat4BufferView.memoryStream.WriteTo(binFile); 635 | ushortBufferView.memoryStream.WriteTo(binFile); 636 | 637 | vec3BufferViewAnim.memoryStream.WriteTo(binFile); 638 | vec4BufferViewAnim.memoryStream.WriteTo(binFile); 639 | 640 | binFile.Flush(); 641 | if (binary) 642 | { 643 | uint fileLength = (uint)fs.Length; 644 | 645 | // write header 646 | fs.Seek(0, SeekOrigin.Begin); 647 | jsonWriter.Write("glTF"); // magic 648 | jsonWriter.Flush(); 649 | binWriter.Write(1); // version 650 | binWriter.Write(fileLength); 651 | binWriter.Write(contentLength); 652 | binWriter.Write(0); // format 653 | binWriter.Flush(); 654 | } 655 | } 656 | } 657 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015, 2016 Tony Parisi. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Preset.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using SimpleJSON; 6 | 7 | public class Preset { 8 | public class Shader { 9 | public string vertexShader; 10 | public string fragmentShader; 11 | } 12 | 13 | public Dictionary shaderMap = new Dictionary(); 14 | 15 | const string DEFAULT_VERTEX_SHADER = "DefaultVS.glsl"; 16 | const string DEFAULT_FRAGMENT_SHADER = "DefaultFS.glsl"; 17 | 18 | public string GetVertexShader(string shaderName) 19 | { 20 | if (shaderMap.ContainsKey(shaderName)) 21 | { 22 | var s = shaderMap[shaderName]; 23 | return s.vertexShader; 24 | } 25 | return DEFAULT_VERTEX_SHADER; 26 | } 27 | 28 | public string GetFragmentShader(string shaderName) 29 | { 30 | if (shaderMap.ContainsKey(shaderName)) 31 | { 32 | var s = shaderMap[shaderName]; 33 | return s.fragmentShader; 34 | } 35 | return DEFAULT_FRAGMENT_SHADER; 36 | } 37 | 38 | public void Load(string path) 39 | { 40 | var text = File.ReadAllText(path); 41 | var obj = JSON.Parse(text); 42 | var sm = obj["ShaderMap"]; 43 | 44 | shaderMap.Clear(); 45 | foreach (var smc in sm.AsObject.Dict) 46 | { 47 | Shader shader = new Shader(); 48 | shader.vertexShader = smc.Value["shaders"]["vertexShader"]; 49 | shader.fragmentShader = smc.Value["shaders"]["fragmentShader"]; 50 | shaderMap[smc.Key] = shader; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important note 2 | 3 | This code is no longer maintained, as the plugin has been moved to https://github.com/sketchfab/UnityGLTF 4 | 5 | The new plugin also includes an importer and a Sketchfab asset browser. 6 | -------------------------------------------------------------------------------- /Resources/ExporterHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sketchfab/Unity-glTF-Exporter/c54295bd5ad40b5f4b99099119fff11d5e5ab92a/Resources/ExporterHeader.png -------------------------------------------------------------------------------- /Resources/dropdown_menu.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sketchfab/Unity-glTF-Exporter/c54295bd5ad40b5f4b99099119fff11d5e5ab92a/Resources/dropdown_menu.JPG -------------------------------------------------------------------------------- /SceneToGlTFWiz.cs: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | GlamExport 3 | - Unity3D Scriptable Wizard to export Hierarchy or Project objects as glTF 4 | 5 | 6 | ****************************************************************************/ 7 | #if UNITY_EDITOR 8 | using UnityEngine; 9 | using UnityEditor; 10 | using System; 11 | using System.Collections; 12 | using System.Collections.Generic; 13 | using System.IO; 14 | using System.IO.Compression; 15 | using System.Text; 16 | using System.Reflection; 17 | using Ionic.Zip; 18 | 19 | public enum IMAGETYPE 20 | { 21 | GRAYSCALE, 22 | RGB, 23 | RGBA, 24 | RGBA_OPAQUE, 25 | R, 26 | G, 27 | B, 28 | A, 29 | G_INVERT, 30 | NORMAL_MAP, 31 | IGNORE 32 | } 33 | 34 | public class SceneToGlTFWiz : MonoBehaviour 35 | { 36 | public int jpgQuality = 85; 37 | 38 | public GlTF_Writer writer; 39 | string savedPath = ""; 40 | int nbSelectedObjects = 0; 41 | 42 | static bool done = true; 43 | 44 | public static void parseUnityCamera(Transform tr) 45 | { 46 | if (tr.GetComponent().orthographic) 47 | { 48 | GlTF_Orthographic cam; 49 | cam = new GlTF_Orthographic(); 50 | cam.type = "orthographic"; 51 | cam.zfar = tr.GetComponent().farClipPlane; 52 | cam.znear = tr.GetComponent().nearClipPlane; 53 | cam.name = GlTF_Writer.cleanNonAlphanumeric(tr.name); 54 | //cam.orthographic.xmag = tr.camera. 55 | GlTF_Writer.cameras.Add(cam); 56 | } 57 | else 58 | { 59 | GlTF_Perspective cam; 60 | cam = new GlTF_Perspective(); 61 | cam.type = "perspective"; 62 | cam.zfar = tr.GetComponent().farClipPlane; 63 | cam.znear = tr.GetComponent().nearClipPlane; 64 | cam.aspect_ratio = tr.GetComponent().aspect; 65 | cam.yfov = tr.GetComponent().fieldOfView; 66 | cam.name = GlTF_Writer.cleanNonAlphanumeric(tr.name); 67 | GlTF_Writer.cameras.Add(cam); 68 | } 69 | } 70 | 71 | public bool isDone() 72 | { 73 | return done; 74 | } 75 | 76 | public void resetParser() 77 | { 78 | done = false; 79 | } 80 | 81 | public static void parseUnityLight(Transform tr) 82 | { 83 | switch (tr.GetComponent().type) 84 | { 85 | case LightType.Point: 86 | GlTF_PointLight pl = new GlTF_PointLight(); 87 | pl.color = new GlTF_ColorRGB(tr.GetComponent().color); 88 | pl.name = GlTF_Writer.cleanNonAlphanumeric(tr.name); 89 | GlTF_Writer.lights.Add(pl); 90 | break; 91 | 92 | case LightType.Spot: 93 | GlTF_SpotLight sl = new GlTF_SpotLight(); 94 | sl.color = new GlTF_ColorRGB(tr.GetComponent().color); 95 | sl.name = GlTF_Writer.cleanNonAlphanumeric(tr.name); 96 | GlTF_Writer.lights.Add(sl); 97 | break; 98 | 99 | case LightType.Directional: 100 | GlTF_DirectionalLight dl = new GlTF_DirectionalLight(); 101 | dl.color = new GlTF_ColorRGB(tr.GetComponent().color); 102 | dl.name = GlTF_Writer.cleanNonAlphanumeric(tr.name); 103 | GlTF_Writer.lights.Add(dl); 104 | break; 105 | 106 | case LightType.Area: 107 | GlTF_AmbientLight al = new GlTF_AmbientLight(); 108 | al.color = new GlTF_ColorRGB(tr.GetComponent().color); 109 | al.name = GlTF_Writer.cleanNonAlphanumeric(tr.name); 110 | GlTF_Writer.lights.Add(al); 111 | break; 112 | } 113 | } 114 | 115 | public void ExportCoroutine(string path, Preset presetAsset, bool buildZip, bool exportPBRMaterials, bool exportAnimation = true, bool doConvertImages = true) 116 | { 117 | StartCoroutine(Export(path, presetAsset, buildZip, exportPBRMaterials, exportAnimation, doConvertImages)); 118 | } 119 | 120 | public int getNbSelectedObjects() 121 | { 122 | return nbSelectedObjects; 123 | } 124 | 125 | public IEnumerator Export(string path, Preset presetAsset, bool buildZip, bool exportPBRMaterials, bool exportAnimation = true, bool doConvertImages = false) 126 | { 127 | writer = new GlTF_Writer(); 128 | writer.Init (); 129 | done = false; 130 | bool debugRightHandedScale = false; 131 | GlTF_Writer.exportedFiles.Clear(); 132 | if (debugRightHandedScale) 133 | GlTF_Writer.convertRightHanded = false; 134 | 135 | writer.extraString.Add("exporterVersion", GlTF_Writer.exporterVersion ); 136 | 137 | // Create rootNode 138 | GlTF_Node correctionNode = new GlTF_Node(); 139 | correctionNode.id = "UnityGlTF_root"; 140 | correctionNode.name = "UnityGlTF_root"; 141 | GlTF_Writer.nodes.Add(correctionNode); 142 | GlTF_Writer.nodeNames.Add(correctionNode.name); 143 | GlTF_Writer.rootNodes.Add(correctionNode); 144 | 145 | //path = toGlTFname(path); 146 | savedPath = Path.GetDirectoryName(path); 147 | 148 | // Temp list to keep track of skeletons 149 | Dictionary parsedSkins = new Dictionary(); 150 | parsedSkins.Clear(); 151 | 152 | // first, collect objects in the scene, add to lists 153 | Transform[] transforms = Selection.GetTransforms (SelectionMode.Deep); 154 | List trs = new List(transforms); 155 | // Prefilter selected nodes and look for skinning in order to list "bones" nodes 156 | //FIXME: improve this 157 | List bones = new List(); 158 | foreach(Transform tr in trs) 159 | { 160 | if (!tr.gameObject.activeSelf) 161 | continue; 162 | 163 | SkinnedMeshRenderer skin = tr.GetComponent(); 164 | if (skin) 165 | { 166 | foreach(Transform bone in skin.bones) 167 | { 168 | bones.Add(bone); 169 | } 170 | } 171 | } 172 | 173 | nbSelectedObjects = trs.Count; 174 | int nbDisabledObjects = 0; 175 | foreach (Transform tr in trs) 176 | { 177 | if (tr.gameObject.activeInHierarchy == false) 178 | { 179 | nbDisabledObjects++; 180 | continue; 181 | } 182 | 183 | // Initialize the node 184 | GlTF_Node node = new GlTF_Node(); 185 | node.id = GlTF_Node.GetNameFromObject(tr); 186 | node.name = GlTF_Writer.cleanNonAlphanumeric(tr.name); 187 | 188 | if (tr.GetComponent() != null) 189 | parseUnityCamera(tr); 190 | 191 | if (tr.GetComponent() != null) 192 | parseUnityLight(tr); 193 | 194 | Mesh m = GetMesh(tr); 195 | if (m != null) 196 | { 197 | GlTF_Mesh mesh = new GlTF_Mesh(); 198 | mesh.name = GlTF_Writer.cleanNonAlphanumeric(GlTF_Mesh.GetNameFromObject(m) + tr.name); 199 | 200 | GlTF_Accessor positionAccessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "position"), GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); 201 | positionAccessor.bufferView = GlTF_Writer.vec3BufferView; 202 | GlTF_Writer.accessors.Add (positionAccessor); 203 | 204 | GlTF_Accessor normalAccessor = null; 205 | if (m.normals.Length > 0) 206 | { 207 | normalAccessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "normal"), GlTF_Accessor.Type.VEC3, GlTF_Accessor.ComponentType.FLOAT); 208 | normalAccessor.bufferView = GlTF_Writer.vec3BufferView; 209 | GlTF_Writer.accessors.Add (normalAccessor); 210 | } 211 | 212 | GlTF_Accessor colorAccessor = null; 213 | if (m.colors.Length > 0) 214 | { 215 | colorAccessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "color"), GlTF_Accessor.Type.VEC4, GlTF_Accessor.ComponentType.FLOAT); 216 | colorAccessor.bufferView = GlTF_Writer.vec4BufferView; 217 | GlTF_Writer.accessors.Add(colorAccessor); 218 | } 219 | 220 | GlTF_Accessor uv0Accessor = null; 221 | if (m.uv.Length > 0) { 222 | uv0Accessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "uv0"), GlTF_Accessor.Type.VEC2, GlTF_Accessor.ComponentType.FLOAT); 223 | uv0Accessor.bufferView = GlTF_Writer.vec2BufferView; 224 | GlTF_Writer.accessors.Add (uv0Accessor); 225 | } 226 | 227 | GlTF_Accessor uv1Accessor = null; 228 | if (m.uv2.Length > 0) { 229 | // check if object is affected by a lightmap 230 | uv1Accessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "uv1"), GlTF_Accessor.Type.VEC2, GlTF_Accessor.ComponentType.FLOAT); 231 | uv1Accessor.bufferView = GlTF_Writer.vec2BufferView; 232 | GlTF_Writer.accessors.Add (uv1Accessor); 233 | } 234 | 235 | GlTF_Accessor uv2Accessor = null; 236 | if (m.uv3.Length > 0) { 237 | uv2Accessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "uv2"), GlTF_Accessor.Type.VEC2, GlTF_Accessor.ComponentType.FLOAT); 238 | uv2Accessor.bufferView = GlTF_Writer.vec2BufferView; 239 | GlTF_Writer.accessors.Add (uv2Accessor); 240 | } 241 | 242 | GlTF_Accessor uv3Accessor = null; 243 | if (m.uv4.Length > 0) { 244 | uv3Accessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "uv3"), GlTF_Accessor.Type.VEC2, GlTF_Accessor.ComponentType.FLOAT); 245 | uv3Accessor.bufferView = GlTF_Writer.vec2BufferView; 246 | GlTF_Writer.accessors.Add (uv3Accessor); 247 | } 248 | 249 | GlTF_Accessor jointAccessor = null; 250 | if (exportAnimation && m.boneWeights.Length > 0) 251 | { 252 | jointAccessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "joints"), GlTF_Accessor.Type.VEC4, GlTF_Accessor.ComponentType.USHORT); 253 | jointAccessor.bufferView = GlTF_Writer.vec4UshortBufferView; 254 | GlTF_Writer.accessors.Add(jointAccessor); 255 | } 256 | 257 | GlTF_Accessor weightAccessor = null; 258 | if (exportAnimation && m.boneWeights.Length > 0) 259 | { 260 | weightAccessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "weights"), GlTF_Accessor.Type.VEC4, GlTF_Accessor.ComponentType.FLOAT); 261 | weightAccessor.bufferView = GlTF_Writer.vec4BufferView; 262 | GlTF_Writer.accessors.Add(weightAccessor); 263 | } 264 | 265 | GlTF_Accessor tangentAccessor = null; 266 | if (m.tangents.Length > 0) 267 | { 268 | tangentAccessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "tangents"), GlTF_Accessor.Type.VEC4, GlTF_Accessor.ComponentType.FLOAT); 269 | tangentAccessor.bufferView = GlTF_Writer.vec4BufferView; 270 | GlTF_Writer.accessors.Add(tangentAccessor); 271 | } 272 | 273 | var smCount = m.subMeshCount; 274 | for (var i = 0; i < smCount; ++i) 275 | { 276 | GlTF_Primitive primitive = new GlTF_Primitive(); 277 | primitive.name = GlTF_Primitive.GetNameFromObject(m, i); 278 | primitive.index = i; 279 | GlTF_Attributes attributes = new GlTF_Attributes(); 280 | attributes.positionAccessor = positionAccessor; 281 | attributes.normalAccessor = normalAccessor; 282 | attributes.colorAccessor = colorAccessor; 283 | attributes.texCoord0Accessor = uv0Accessor; 284 | attributes.texCoord1Accessor = uv1Accessor; 285 | attributes.texCoord2Accessor = uv2Accessor; 286 | attributes.texCoord3Accessor = uv3Accessor; 287 | attributes.jointAccessor = jointAccessor; 288 | attributes.weightAccessor = weightAccessor; 289 | attributes.tangentAccessor = tangentAccessor; 290 | primitive.attributes = attributes; 291 | GlTF_Accessor indexAccessor = new GlTF_Accessor(GlTF_Accessor.GetNameFromObject(m, "indices_" + i), GlTF_Accessor.Type.SCALAR, GlTF_Accessor.ComponentType.USHORT); 292 | indexAccessor.bufferView = GlTF_Writer.ushortBufferView; 293 | GlTF_Writer.accessors.Add (indexAccessor); 294 | primitive.indices = indexAccessor; 295 | 296 | var mr = GetRenderer(tr); 297 | var sm = mr.sharedMaterials; 298 | if (i < sm.Length) { 299 | var mat = sm[i]; 300 | var matName = GlTF_Material.GetNameFromObject(mat); 301 | if(GlTF_Writer.materialNames.Contains(matName)) 302 | { 303 | primitive.materialIndex = GlTF_Writer.materialNames.IndexOf(matName); // THIS INDIRECTION CAN BE REMOVED! 304 | } 305 | else 306 | { 307 | GlTF_Material material = new GlTF_Material(); 308 | material.name = GlTF_Writer.cleanNonAlphanumeric(mat.name); 309 | primitive.materialIndex = GlTF_Writer.materials.Count; 310 | GlTF_Writer.materialNames.Add(matName); 311 | GlTF_Writer.materials.Add (material); 312 | 313 | //technique 314 | var s = mat.shader; 315 | var techName = GlTF_Technique.GetNameFromObject(s); 316 | if(GlTF_Writer.techniqueNames.Contains(techName)) 317 | { 318 | material.instanceTechniqueIndex = GlTF_Writer.techniqueNames.IndexOf(techName);// THIS INDIRECTION CAN BE REMOVED! 319 | } 320 | else 321 | { 322 | GlTF_Technique tech = new GlTF_Technique(); 323 | tech.name = techName; 324 | GlTF_Technique.Parameter tParam = new GlTF_Technique.Parameter(); 325 | tParam.name = "position"; 326 | tParam.type = GlTF_Technique.Type.FLOAT_VEC3; 327 | tParam.semantic = GlTF_Technique.Semantic.POSITION; 328 | tech.parameters.Add(tParam); 329 | GlTF_Technique.Attribute tAttr = new GlTF_Technique.Attribute(); 330 | tAttr.name = "a_position"; 331 | tAttr.param = tParam.name; 332 | tech.attributes.Add(tAttr); 333 | 334 | if (normalAccessor != null) 335 | { 336 | tParam = new GlTF_Technique.Parameter(); 337 | tParam.name = "normal"; 338 | tParam.type = GlTF_Technique.Type.FLOAT_VEC3; 339 | tParam.semantic = GlTF_Technique.Semantic.NORMAL; 340 | tech.parameters.Add(tParam); 341 | tAttr = new GlTF_Technique.Attribute(); 342 | tAttr.name = "a_normal"; 343 | tAttr.param = tParam.name; 344 | tech.attributes.Add(tAttr); 345 | } 346 | 347 | if (uv0Accessor != null) 348 | { 349 | tParam = new GlTF_Technique.Parameter(); 350 | tParam.name = "texcoord0"; 351 | tParam.type = GlTF_Technique.Type.FLOAT_VEC2; 352 | tParam.semantic = GlTF_Technique.Semantic.TEXCOORD_0; 353 | tech.parameters.Add(tParam); 354 | tAttr = new GlTF_Technique.Attribute(); 355 | tAttr.name = "a_texcoord0"; 356 | tAttr.param = tParam.name; 357 | tech.attributes.Add(tAttr); 358 | } 359 | 360 | if (uv1Accessor != null) 361 | { 362 | tParam = new GlTF_Technique.Parameter(); 363 | tParam.name = "texcoord1"; 364 | tParam.type = GlTF_Technique.Type.FLOAT_VEC2; 365 | tParam.semantic = GlTF_Technique.Semantic.TEXCOORD_1; 366 | tech.parameters.Add(tParam); 367 | tAttr = new GlTF_Technique.Attribute(); 368 | tAttr.name = "a_texcoord1"; 369 | tAttr.param = tParam.name; 370 | tech.attributes.Add(tAttr); 371 | } 372 | 373 | if (uv2Accessor != null) 374 | { 375 | tParam = new GlTF_Technique.Parameter(); 376 | tParam.name = "texcoord2"; 377 | tParam.type = GlTF_Technique.Type.FLOAT_VEC2; 378 | tParam.semantic = GlTF_Technique.Semantic.TEXCOORD_2; 379 | tech.parameters.Add(tParam); 380 | tAttr = new GlTF_Technique.Attribute(); 381 | tAttr.name = "a_texcoord2"; 382 | tAttr.param = tParam.name; 383 | tech.attributes.Add(tAttr); 384 | } 385 | 386 | if (uv3Accessor != null) 387 | { 388 | tParam = new GlTF_Technique.Parameter(); 389 | tParam.name = "texcoord3"; 390 | tParam.type = GlTF_Technique.Type.FLOAT_VEC2; 391 | tParam.semantic = GlTF_Technique.Semantic.TEXCOORD_3; 392 | tech.parameters.Add(tParam); 393 | tAttr = new GlTF_Technique.Attribute(); 394 | tAttr.name = "a_texcoord3"; 395 | tAttr.param = tParam.name; 396 | tech.attributes.Add(tAttr); 397 | } 398 | 399 | tech.AddDefaultUniforms(); 400 | 401 | // Populate technique with shader data 402 | GlTF_Writer.techniqueNames.Add (techName); 403 | GlTF_Writer.techniques.Add (tech); 404 | 405 | // create program 406 | GlTF_Program program = new GlTF_Program(); 407 | program.name = GlTF_Program.GetNameFromObject(s); 408 | tech.program = program.name; 409 | foreach (var attr in tech.attributes) 410 | { 411 | program.attributes.Add(attr.name); 412 | } 413 | GlTF_Writer.programs.Add(program); 414 | } 415 | 416 | unityToPBRMaterial(mat, ref material); 417 | } 418 | } 419 | mesh.primitives.Add(primitive); 420 | } 421 | 422 | // If gameobject having SkinnedMeshRenderer component has been transformed, 423 | // the mesh would need to be baked here. 424 | mesh.Populate(m); 425 | GlTF_Writer.meshes.Add(mesh); 426 | node.meshIndex = GlTF_Writer.meshes.IndexOf(mesh); 427 | } 428 | 429 | // Parse animations 430 | if (exportAnimation) 431 | { 432 | Animator a = tr.GetComponent(); 433 | if (a != null) 434 | { 435 | AnimationClip[] clips = AnimationUtility.GetAnimationClips(tr.gameObject); 436 | for (int i = 0; i < clips.Length; i++) 437 | { 438 | //FIXME It seems not good to generate one animation per animator. 439 | GlTF_Animation anim = new GlTF_Animation(GlTF_Writer.cleanNonAlphanumeric(a.name)); 440 | anim.Populate(clips[i], tr, GlTF_Writer.bakeAnimation); 441 | if(anim.channels.Count > 0) 442 | GlTF_Writer.animations.Add(anim); 443 | } 444 | } 445 | 446 | Animation animation = tr.GetComponent(); 447 | if (animation != null) 448 | { 449 | AnimationClip clip = animation.clip; 450 | //FIXME It seems not good to generate one animation per animator. 451 | GlTF_Animation anim = new GlTF_Animation(GlTF_Writer.cleanNonAlphanumeric(animation.name)); 452 | anim.Populate(clip, tr, GlTF_Writer.bakeAnimation); 453 | if (anim.channels.Count > 0) 454 | GlTF_Writer.animations.Add(anim); 455 | } 456 | } 457 | 458 | // Parse transform 459 | if (tr.parent == null) 460 | { 461 | Matrix4x4 mat = Matrix4x4.identity; 462 | if(debugRightHandedScale) 463 | mat.m22 = -1; 464 | mat = mat * Matrix4x4.TRS(tr.localPosition, tr.localRotation, tr.localScale); 465 | node.matrix = new GlTF_Matrix(mat); 466 | } 467 | // Use good transform if parent object is not in selection 468 | else if (!trs.Contains(tr.parent)) 469 | { 470 | node.hasParent = false; 471 | Matrix4x4 mat = Matrix4x4.identity; 472 | if(debugRightHandedScale) 473 | mat.m22 = -1; 474 | mat = mat * tr.localToWorldMatrix; 475 | node.matrix = new GlTF_Matrix(mat); 476 | } 477 | else 478 | { 479 | node.hasParent = true; 480 | if (tr.localPosition != Vector3.zero) 481 | node.translation = new GlTF_Translation (tr.localPosition); 482 | if (tr.localScale != Vector3.one) 483 | node.scale = new GlTF_Scale (tr.localScale); 484 | if (tr.localRotation != Quaternion.identity) 485 | node.rotation = new GlTF_Rotation (tr.localRotation); 486 | } 487 | 488 | if(!node.hasParent) 489 | correctionNode.childrenNames.Add(node.id); 490 | 491 | if (tr.GetComponent() != null) 492 | { 493 | node.cameraName = GlTF_Writer.cleanNonAlphanumeric(tr.name); 494 | } 495 | else if (tr.GetComponent() != null) 496 | node.lightName = GlTF_Writer.cleanNonAlphanumeric(tr.name); 497 | 498 | // Parse node's skin data 499 | GlTF_Accessor invBindMatrixAccessor = null; 500 | SkinnedMeshRenderer skinMesh = tr.GetComponent(); 501 | if (exportAnimation && skinMesh != null && skinMesh.enabled && checkSkinValidity(skinMesh, trs) && skinMesh.rootBone != null) 502 | { 503 | GlTF_Skin skin = new GlTF_Skin(); 504 | 505 | skin.name = GlTF_Writer.cleanNonAlphanumeric(skinMesh.rootBone.name) + "_skeleton_" + GlTF_Writer.cleanNonAlphanumeric(node.name) + tr.GetInstanceID(); 506 | 507 | // Create invBindMatrices accessor 508 | invBindMatrixAccessor = new GlTF_Accessor(skin.name + "invBindMatrices", GlTF_Accessor.Type.MAT4, GlTF_Accessor.ComponentType.FLOAT); 509 | invBindMatrixAccessor.bufferView = GlTF_Writer.mat4BufferView; 510 | GlTF_Writer.accessors.Add(invBindMatrixAccessor); 511 | 512 | // Generate skin data 513 | skin.Populate(tr, ref invBindMatrixAccessor, GlTF_Writer.accessors.Count -1); 514 | GlTF_Writer.skins.Add(skin); 515 | node.skinIndex = GlTF_Writer.skins.IndexOf(skin); 516 | } 517 | 518 | foreach (Transform t in tr.transform) 519 | { 520 | if(t.gameObject.activeInHierarchy) 521 | node.childrenNames.Add(GlTF_Node.GetNameFromObject(t)); 522 | } 523 | 524 | GlTF_Writer.nodeNames.Add(node.id); 525 | GlTF_Writer.nodes.Add (node); 526 | } 527 | 528 | if (GlTF_Writer.meshes.Count == 0) 529 | { 530 | Debug.Log("No visible objects have been exported. Aboring export"); 531 | yield return false; 532 | } 533 | 534 | writer.OpenFiles(path); 535 | writer.Write (); 536 | writer.CloseFiles(); 537 | 538 | if(nbDisabledObjects > 0) 539 | Debug.Log(nbDisabledObjects + " disabled object ignored during export"); 540 | 541 | Debug.Log("Scene has been exported to " + path); 542 | if(buildZip) 543 | { 544 | ZipFile zip = new ZipFile(); 545 | Debug.Log(GlTF_Writer.exportedFiles.Count + " files generated"); 546 | string zipName = Path.GetFileNameWithoutExtension(path) + ".zip"; 547 | foreach(string originFilePath in GlTF_Writer.exportedFiles.Keys) 548 | { 549 | zip.AddFile(originFilePath, GlTF_Writer.exportedFiles[originFilePath]); 550 | } 551 | 552 | zip.Save(savedPath + "/" + zipName); 553 | 554 | // Remove all files 555 | foreach (string pa in GlTF_Writer.exportedFiles.Keys) 556 | { 557 | if (System.IO.File.Exists(pa)) 558 | System.IO.File.Delete(pa); 559 | } 560 | 561 | Debug.Log("Files have been cleaned"); 562 | } 563 | done = true; 564 | 565 | yield return true; 566 | } 567 | 568 | // Check if all the bones referenced by the skin are in the selection 569 | public bool checkSkinValidity(SkinnedMeshRenderer skin, List selection) 570 | { 571 | string unselected = ""; 572 | foreach(Transform t in skin.bones) 573 | { 574 | if (!selection.Contains(t)) 575 | { 576 | unselected = unselected + "\n" + t.name; 577 | } 578 | } 579 | 580 | if(unselected.Length > 0) 581 | { 582 | Debug.LogError("Error while exportin skin for " + skin.name + " (skipping skinning export).\nClick for more details:\n \nThe following bones are used but are not selected" + unselected + "\n"); 583 | return false; 584 | } 585 | 586 | return true; 587 | } 588 | 589 | private string toGlTFname(string name) 590 | { 591 | // remove spaces and illegal chars, replace with underscores 592 | string correctString = name.Replace(" ", "_"); 593 | // make sure it doesn't start with a number 594 | return correctString; 595 | } 596 | 597 | private bool isInheritedFrom (Type t, Type baseT) 598 | { 599 | if (t == baseT) 600 | return true; 601 | t = t.BaseType; 602 | while (t != null && t != typeof(System.Object)) 603 | { 604 | if (t == baseT) 605 | return true; 606 | t = t.BaseType; 607 | } 608 | return false; 609 | } 610 | 611 | private Renderer GetRenderer(Transform tr) 612 | { 613 | Renderer mr = tr.GetComponent(); 614 | if (mr == null) { 615 | mr = tr.GetComponent(); 616 | } 617 | return mr; 618 | } 619 | 620 | private void clampColor(ref Color c) 621 | { 622 | c.r = c.r > 1.0f ? 1.0f : c.r; 623 | c.g = c.g > 1.0f ? 1.0f : c.g; 624 | c.b = c.b > 1.0f ? 1.0f : c.b; 625 | //c.a = c.a > 1.0f ? 1.0f : c.a; 626 | } 627 | 628 | private Mesh GetMesh(Transform tr) 629 | { 630 | var mr = GetRenderer(tr); 631 | Mesh m = null; 632 | if (mr != null && mr.enabled) 633 | { 634 | var t = mr.GetType(); 635 | if (t == typeof(MeshRenderer)) 636 | { 637 | MeshFilter mf = tr.GetComponent(); 638 | if(!mf) 639 | { 640 | Debug.Log("The gameObject " + tr.name + " will be exported as Transform (object has no MeshFilter component attached)"); 641 | return null; 642 | } 643 | m = mf.sharedMesh; 644 | } else if (t == typeof(SkinnedMeshRenderer)) 645 | { 646 | SkinnedMeshRenderer smr = mr as SkinnedMeshRenderer; 647 | m = smr.sharedMesh; 648 | } 649 | } 650 | return m; 651 | } 652 | 653 | private bool handleTransparency(ref Material mat, ref GlTF_Material material) 654 | { 655 | if (mat.HasProperty("_Mode") && mat.GetFloat("_Mode") != 0) 656 | { 657 | string mode = mat.GetFloat("_Mode") == 1 ? "MASK" : "BLEND"; 658 | GlTF_Material.StringValue alphaMode = new GlTF_Material.StringValue(); 659 | alphaMode.name = "alphaMode"; 660 | alphaMode.value = mode; 661 | 662 | GlTF_Material.FloatValue alphaCutoff = new GlTF_Material.FloatValue(); 663 | alphaCutoff.name = "alphaCutoff"; 664 | alphaCutoff.value = mat.GetFloat("_Cutoff"); 665 | 666 | material.values.Add(alphaMode); 667 | material.values.Add(alphaCutoff); 668 | 669 | return true; 670 | } 671 | 672 | return false; 673 | } 674 | 675 | private void addTexturePixels(ref Texture2D texture, ref Color[] colors, IMAGETYPE outputChannel, IMAGETYPE inputChannel = IMAGETYPE.R) 676 | { 677 | int height = texture.height; 678 | int width = texture.width; 679 | Color[] inputColors = new Color[texture.width * texture.height]; 680 | if (!texture || !getPixelsFromTexture(ref texture, out inputColors)) 681 | return; 682 | 683 | if(height * width != colors.Length) 684 | { 685 | Debug.Log("Issue with texture dimensions"); 686 | return; 687 | } 688 | 689 | if(inputChannel != IMAGETYPE.R && inputChannel != IMAGETYPE.A) 690 | { 691 | Debug.Log("Incorrect input channel (only 'R' and 'A' supported)"); 692 | } 693 | 694 | for (int i = 0; i < height; ++i) 695 | { 696 | for (int j = 0; j < width; ++j) 697 | { 698 | int index = i * width + j; 699 | int newIndex = (height - i - 1) * width + j; 700 | Color c = outputChannel == IMAGETYPE.RGB ? inputColors[newIndex] : colors[index]; 701 | float inputValue = inputChannel == IMAGETYPE.R ? inputColors[newIndex].r : inputColors[newIndex].a; 702 | 703 | if(outputChannel == IMAGETYPE.R) 704 | { 705 | c.r = inputValue; 706 | } 707 | else if(outputChannel == IMAGETYPE.G) 708 | { 709 | c.g = inputValue; 710 | } 711 | else if(outputChannel == IMAGETYPE.B) 712 | { 713 | c.b = inputValue; 714 | } 715 | else if(outputChannel == IMAGETYPE.G_INVERT) 716 | { 717 | c.g = 1.0f - inputValue; 718 | } 719 | 720 | colors[index] = c; 721 | } 722 | } 723 | 724 | } 725 | 726 | private int createOcclusionMetallicRoughnessTexture(ref Texture2D occlusion, ref Texture2D metallicRoughness) 727 | { 728 | string texName = ""; 729 | int width = -1; 730 | int height = -1; 731 | string assetPath = ""; 732 | if(occlusion) 733 | { 734 | texName = texName + GlTF_Texture.GetNameFromObject(occlusion); 735 | assetPath = AssetDatabase.GetAssetPath(occlusion); 736 | width = occlusion.width; 737 | height = occlusion.height; 738 | } 739 | else 740 | { 741 | texName = texName + "_"; 742 | } 743 | 744 | if (metallicRoughness) 745 | { 746 | texName = texName + GlTF_Texture.GetNameFromObject(metallicRoughness); 747 | assetPath = AssetDatabase.GetAssetPath(metallicRoughness); 748 | width = metallicRoughness.width; 749 | height = metallicRoughness.height; 750 | } 751 | else 752 | { 753 | texName = texName + "_"; 754 | } 755 | 756 | if (!GlTF_Writer.textureNames.Contains(texName)) 757 | { 758 | // Create texture 759 | GlTF_Texture texture = new GlTF_Texture(); 760 | texture.name = texName; 761 | 762 | // Export image 763 | GlTF_Image img = new GlTF_Image(); 764 | img.name = texName; 765 | //img.uri = 766 | 767 | // Let's consider that the three textures have the same resolution 768 | Color[] outputColors = new Color[width * height]; 769 | for (int i = 0; i < outputColors.Length; ++i) 770 | outputColors[i] = new Color(1.0f, 1.0f, 1.0f); 771 | 772 | if (occlusion) 773 | addTexturePixels(ref occlusion, ref outputColors, IMAGETYPE.R); 774 | if (metallicRoughness) 775 | { 776 | addTexturePixels(ref metallicRoughness, ref outputColors, IMAGETYPE.B); 777 | addTexturePixels(ref metallicRoughness, ref outputColors, IMAGETYPE.G_INVERT, IMAGETYPE.A); 778 | } 779 | 780 | Texture2D newtex = new Texture2D(width, height); 781 | newtex.SetPixels(outputColors); 782 | newtex.Apply(); 783 | 784 | string pathInArchive = Path.GetDirectoryName(assetPath); 785 | string exportDir = Path.Combine(savedPath, pathInArchive); 786 | 787 | if (!Directory.Exists(exportDir)) 788 | Directory.CreateDirectory(exportDir); 789 | 790 | string outputFilename = Path.GetFileNameWithoutExtension(assetPath) + "_converted_metalRoughness.jpg"; 791 | string exportPath = exportDir + "/" + outputFilename; // relative path inside the .zip 792 | File.WriteAllBytes(exportPath, newtex.EncodeToJPG(jpgQuality)); 793 | 794 | if (!GlTF_Writer.exportedFiles.ContainsKey(exportPath)) 795 | GlTF_Writer.exportedFiles.Add(exportPath, pathInArchive); 796 | else 797 | Debug.LogError("Texture '" + newtex.name + "' already exists"); 798 | 799 | img.uri = pathInArchive + "/" + outputFilename; 800 | 801 | texture.source = GlTF_Writer.imageNames.Count; 802 | GlTF_Writer.imageNames.Add(img.name); 803 | GlTF_Writer.images.Add(img); 804 | 805 | // Add sampler 806 | GlTF_Sampler sampler; 807 | var samplerName = GlTF_Sampler.GetNameFromObject(metallicRoughness); 808 | if (!GlTF_Writer.samplerNames.Contains(samplerName)) 809 | { 810 | sampler = new GlTF_Sampler(metallicRoughness); 811 | sampler.name = samplerName; 812 | GlTF_Writer.samplers.Add(sampler); 813 | GlTF_Writer.samplerNames.Add(samplerName); 814 | } 815 | 816 | GlTF_Writer.textures.Add(texture); 817 | GlTF_Writer.textureNames.Add(texName); 818 | } 819 | 820 | return GlTF_Writer.textureNames.IndexOf(texName); 821 | 822 | } 823 | 824 | // Get or create texture object, image and sampler 825 | private int processTexture(Texture2D t, IMAGETYPE format) 826 | { 827 | var texName = GlTF_Texture.GetNameFromObject(t); 828 | if (AssetDatabase.GetAssetPath(t).Length == 0) 829 | { 830 | Debug.LogWarning("Texture " + t.name + " cannot be found in assets"); 831 | return -1; 832 | } 833 | 834 | if (!GlTF_Writer.textureNames.Contains(texName)) 835 | { 836 | string assetPath = AssetDatabase.GetAssetPath(t); 837 | 838 | // Create texture 839 | GlTF_Texture texture = new GlTF_Texture(); 840 | texture.name = texName; 841 | 842 | // Export image 843 | GlTF_Image img = new GlTF_Image(); 844 | img.name = GlTF_Image.GetNameFromObject(t); 845 | img.uri = convertTexture(ref t, assetPath, savedPath, format); 846 | 847 | texture.source = GlTF_Writer.imageNames.Count; 848 | GlTF_Writer.imageNames.Add(img.name); 849 | GlTF_Writer.images.Add(img); 850 | 851 | // Add sampler 852 | GlTF_Sampler sampler; 853 | var samplerName = GlTF_Sampler.GetNameFromObject(t); 854 | if (!GlTF_Writer.samplerNames.Contains(samplerName)) 855 | { 856 | sampler = new GlTF_Sampler(t); 857 | sampler.name = samplerName; 858 | GlTF_Writer.samplers.Add(sampler); 859 | GlTF_Writer.samplerNames.Add(samplerName); 860 | } 861 | 862 | GlTF_Writer.textures.Add(texture); 863 | GlTF_Writer.textureNames.Add(texName); 864 | } 865 | 866 | return GlTF_Writer.textureNames.IndexOf(texName); 867 | } 868 | 869 | // Convert material from Unity to glTF PBR 870 | private void unityToPBRMaterial(Material mat, ref GlTF_Material material) 871 | { 872 | bool isMaterialPBR = true; 873 | bool isMetal = true; 874 | bool hasPBRMap = false; 875 | 876 | if (!mat.shader.name.Contains("Standard")) 877 | { 878 | Debug.Log("Material " + mat.shader + " is not fully supported"); 879 | isMaterialPBR = false; 880 | } 881 | else 882 | { 883 | // Is metal workflow used 884 | isMetal = mat.shader.name == "Standard"; 885 | GlTF_Writer.hasSpecularMaterials = GlTF_Writer.hasSpecularMaterials || !isMetal; 886 | material.isMetal = isMetal; 887 | 888 | // Is smoothness defined by diffuse texture or PBR texture' alpha? 889 | if (mat.GetFloat("_SmoothnessTextureChannel") != 0) 890 | Debug.Log("Smoothness uses diffuse's alpha channel. Unsupported for now"); 891 | 892 | hasPBRMap = (!isMetal && mat.GetTexture("_SpecGlossMap") != null || isMetal && mat.GetTexture("_MetallicGlossMap") != null); 893 | } 894 | 895 | //Check transparency 896 | bool hasTransparency = handleTransparency(ref mat, ref material); 897 | 898 | //Parse diffuse channel texture and color 899 | if (mat.HasProperty("_MainTex") && mat.GetTexture("_MainTex") != null) 900 | { 901 | var textureValue = new GlTF_Material.DictValue(); 902 | textureValue.name = isMetal ? "baseColorTexture" : "diffuseTexture"; 903 | 904 | int diffuseTextureIndex = processTexture((Texture2D)mat.GetTexture("_MainTex"), hasTransparency ? IMAGETYPE.RGBA : IMAGETYPE.RGBA_OPAQUE); 905 | textureValue.intValue.Add("index", diffuseTextureIndex); 906 | textureValue.intValue.Add("texCoord", 0); 907 | material.pbrValues.Add(textureValue); 908 | } 909 | 910 | if (mat.HasProperty("_Color")) 911 | { 912 | var colorValue = new GlTF_Material.ColorValue(); 913 | colorValue.name = isMetal ? "baseColorFactor" : "diffuseFactor"; 914 | Color c = mat.GetColor("_Color"); 915 | clampColor(ref c); 916 | colorValue.color = c; 917 | material.pbrValues.Add(colorValue); 918 | } 919 | 920 | //Parse PBR textures 921 | if (isMaterialPBR) 922 | { 923 | if (isMetal) 924 | { 925 | if (hasPBRMap) // No metallic factor if texture 926 | { 927 | var textureValue = new GlTF_Material.DictValue(); 928 | textureValue.name = "metallicRoughnessTexture"; 929 | Texture2D metallicRoughnessTexture = (Texture2D)mat.GetTexture("_MetallicGlossMap"); 930 | Texture2D occlusion = (Texture2D)mat.GetTexture("_OcclusionMap"); 931 | int metalRoughTextureIndex = createOcclusionMetallicRoughnessTexture (ref occlusion, ref metallicRoughnessTexture); 932 | textureValue.intValue.Add("index", metalRoughTextureIndex); 933 | textureValue.intValue.Add("texCoord", 0); 934 | material.pbrValues.Add(textureValue); 935 | } 936 | 937 | var metallicFactor = new GlTF_Material.FloatValue(); 938 | metallicFactor.name = "metallicFactor"; 939 | metallicFactor.value = hasPBRMap ? 1.0f : mat.GetFloat("_Metallic"); 940 | material.pbrValues.Add(metallicFactor); 941 | 942 | //Roughness factor 943 | var roughnessFactor = new GlTF_Material.FloatValue(); 944 | roughnessFactor.name = "roughnessFactor"; 945 | roughnessFactor.value = hasPBRMap ? 1.0f : 1 - mat.GetFloat("_Glossiness"); // gloss scale is not supported for now(property _GlossMapScale) 946 | material.pbrValues.Add(roughnessFactor); 947 | } 948 | else 949 | { 950 | if (hasPBRMap) // No metallic factor if texture 951 | { 952 | var textureValue = new GlTF_Material.DictValue(); 953 | textureValue.name = "specularGlossinessTexture"; 954 | int specGlossTextureIndex = processTexture((Texture2D)mat.GetTexture("_SpecGlossMap"), IMAGETYPE.RGBA); 955 | textureValue.intValue.Add("index", specGlossTextureIndex); 956 | textureValue.intValue.Add("texCoord", 0); 957 | material.pbrValues.Add(textureValue); 958 | } 959 | 960 | var specularFactor = new GlTF_Material.ColorValue(); 961 | specularFactor.name = "specularFactor"; 962 | specularFactor.color = hasPBRMap ? Color.white : mat.GetColor("_SpecColor"); // gloss scale is not supported for now(property _GlossMapScale) 963 | specularFactor.isRGB = true; 964 | material.pbrValues.Add(specularFactor); 965 | 966 | var glossinessFactor = new GlTF_Material.FloatValue(); 967 | glossinessFactor.name = "glossinessFactor"; 968 | glossinessFactor.value = hasPBRMap ? 1.0f : mat.GetFloat("_Glossiness"); // gloss scale is not supported for now(property _GlossMapScale) 969 | material.pbrValues.Add(glossinessFactor); 970 | } 971 | } 972 | 973 | //BumpMap 974 | if (mat.HasProperty("_BumpMap") && mat.GetTexture("_BumpMap") != null) 975 | { 976 | Texture2D bumpTexture = mat.GetTexture("_BumpMap") as Texture2D; 977 | // Check if it's a normal or a bump map 978 | TextureImporter im = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(bumpTexture)) as TextureImporter; 979 | bool isBumpMap = im.convertToNormalmap; 980 | 981 | if(isBumpMap) 982 | { 983 | Debug.LogWarning("Unsupported texture " + bumpTexture + " (normal maps generated from grayscale are not supported)"); 984 | } 985 | else 986 | { 987 | var textureValue = new GlTF_Material.DictValue(); 988 | textureValue.name = "normalTexture"; 989 | 990 | int bumpTextureIndex = processTexture(bumpTexture, IMAGETYPE.NORMAL_MAP); 991 | textureValue.intValue.Add("index", bumpTextureIndex); 992 | textureValue.intValue.Add("texCoord", 0); 993 | textureValue.floatValue.Add("scale", mat.GetFloat("_BumpScale")); 994 | material.values.Add(textureValue); 995 | } 996 | } 997 | 998 | //Emissive 999 | if (mat.HasProperty("_EmissionMap") && mat.GetTexture("_EmissionMap") != null) 1000 | { 1001 | Texture2D emissiveTexture = mat.GetTexture("_EmissionMap") as Texture2D; 1002 | var textureValue = new GlTF_Material.DictValue(); 1003 | textureValue.name = "emissiveTexture"; 1004 | 1005 | int emissiveTextureIndex = processTexture(emissiveTexture, IMAGETYPE.RGB); 1006 | textureValue.intValue.Add("index", emissiveTextureIndex); 1007 | textureValue.intValue.Add("texCoord", 0); 1008 | material.values.Add(textureValue); 1009 | } 1010 | 1011 | var emissiveFactor = new GlTF_Material.ColorValue(); 1012 | emissiveFactor.name = "emissiveFactor"; 1013 | emissiveFactor.isRGB = true; 1014 | emissiveFactor.color = mat.GetColor("_EmissionColor"); 1015 | material.values.Add(emissiveFactor); 1016 | 1017 | //Occlusion (kept as separated channel for specular workflow, but merged in R channel for metallic workflow) 1018 | if (mat.HasProperty("_OcclusionMap") && mat.GetTexture("_OcclusionMap") != null) 1019 | { 1020 | Texture2D occlusionTexture = mat.GetTexture("_OcclusionMap") as Texture2D; 1021 | var textureValue = new GlTF_Material.DictValue(); 1022 | textureValue.name = "occlusionTexture"; 1023 | 1024 | int occlusionTextureIndex = processTexture(occlusionTexture, IMAGETYPE.RGB); 1025 | textureValue.intValue.Add("index", occlusionTextureIndex); 1026 | textureValue.intValue.Add("texCoord", 0); 1027 | textureValue.floatValue.Add("strength", mat.GetFloat("_OcclusionStrength")); 1028 | material.values.Add(textureValue); 1029 | } 1030 | 1031 | // Unity materials are single sided by default 1032 | GlTF_Material.BoolValue doubleSided = new GlTF_Material.BoolValue(); 1033 | doubleSided.name = "doubleSided"; 1034 | doubleSided.value = false; 1035 | material.values.Add(doubleSided); 1036 | } 1037 | private bool getPixelsFromTexture(ref Texture2D texture, out Color[] pixels) 1038 | { 1039 | //Make texture readable 1040 | TextureImporter im = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture)) as TextureImporter; 1041 | if(!im) 1042 | { 1043 | pixels = new Color[1]; 1044 | return false; 1045 | } 1046 | bool readable = im.isReadable; 1047 | #if UNITY_5_4 1048 | TextureImporterFormat format = im.textureFormat; 1049 | #else 1050 | TextureImporterCompression format = im.textureCompression; 1051 | #endif 1052 | TextureImporterType type = im.textureType; 1053 | bool isConvertedBump = im.convertToNormalmap; 1054 | 1055 | if (!readable) 1056 | im.isReadable = true; 1057 | #if UNITY_5_4 1058 | if (type != TextureImporterType.Image) 1059 | im.textureType = TextureImporterType.Image; 1060 | im.textureFormat = TextureImporterFormat.ARGB32; 1061 | #else 1062 | if (type != TextureImporterType.Default) 1063 | im.textureType = TextureImporterType.Default; 1064 | 1065 | im.textureCompression = TextureImporterCompression.Uncompressed; 1066 | #endif 1067 | im.SaveAndReimport(); 1068 | 1069 | pixels = texture.GetPixels(); 1070 | 1071 | if (!readable) 1072 | im.isReadable = false; 1073 | #if UNITY_5_4 1074 | if (type != TextureImporterType.Image) 1075 | im.textureType = type; 1076 | #else 1077 | if (type != TextureImporterType.Default) 1078 | im.textureType = type; 1079 | #endif 1080 | if (isConvertedBump) 1081 | im.convertToNormalmap = true; 1082 | 1083 | #if UNITY_5_4 1084 | im.textureFormat = format; 1085 | #else 1086 | im.textureCompression = format; 1087 | #endif 1088 | 1089 | im.SaveAndReimport(); 1090 | 1091 | return true; 1092 | } 1093 | 1094 | // Flip all images on Y and 1095 | public string convertTexture(ref Texture2D inputTexture, string pathInProject, string exportDirectory, IMAGETYPE format) 1096 | { 1097 | int height = inputTexture.height; 1098 | int width = inputTexture.width; 1099 | Color[] textureColors = new Color[inputTexture.height * inputTexture.width]; 1100 | if(!getPixelsFromTexture(ref inputTexture, out textureColors)) 1101 | { 1102 | Debug.Log("Failed to convert texture " + inputTexture.name + " (unsupported type or format)"); 1103 | return ""; 1104 | } 1105 | Color[] newTextureColors = new Color[inputTexture.height * inputTexture.width]; 1106 | 1107 | for (int i = 0; i < height; ++i) 1108 | { 1109 | for (int j = 0; j < width; ++j) 1110 | { 1111 | newTextureColors[i * width + j] = textureColors[(height - i - 1) * width + j]; 1112 | if (format == IMAGETYPE.RGBA_OPAQUE) 1113 | newTextureColors[i * width + j].a = 1.0f; 1114 | } 1115 | } 1116 | 1117 | Texture2D newtex = new Texture2D(inputTexture.width, inputTexture.height); 1118 | newtex.SetPixels(newTextureColors); 1119 | newtex.Apply(); 1120 | 1121 | string pathInArchive = Path.GetDirectoryName(pathInProject); 1122 | string exportDir = Path.Combine(exportDirectory, pathInArchive); 1123 | 1124 | if (!Directory.Exists(exportDir)) 1125 | Directory.CreateDirectory(exportDir); 1126 | 1127 | string outputFilename = Path.GetFileNameWithoutExtension(pathInProject) + (format == IMAGETYPE.RGBA ? ".png" : ".jpg"); 1128 | string exportPath = exportDir + "/" + outputFilename; // relative path inside the .zip 1129 | string pathInGltfFile = pathInArchive + "/" + outputFilename; 1130 | File.WriteAllBytes(exportPath, (format == IMAGETYPE.RGBA ? newtex.EncodeToPNG() : newtex.EncodeToJPG( format== IMAGETYPE.NORMAL_MAP ? 95 : jpgQuality))); 1131 | 1132 | if (!GlTF_Writer.exportedFiles.ContainsKey(exportPath)) 1133 | GlTF_Writer.exportedFiles.Add(exportPath, pathInArchive); 1134 | else 1135 | Debug.LogError("Texture '" + inputTexture + "' already exists"); 1136 | 1137 | return pathInGltfFile; 1138 | } 1139 | } 1140 | #endif -------------------------------------------------------------------------------- /SimpleJSON.cs: -------------------------------------------------------------------------------- 1 | //#define USE_SharpZipLib 2 | #if !UNITY_WEBPLAYER 3 | #define USE_FileIO 4 | #endif 5 | /* * * * * 6 | * A simple JSON Parser / builder 7 | * ------------------------------ 8 | * 9 | * It mainly has been written as a simple JSON parser. It can build a JSON string 10 | * from the node-tree, or generate a node tree from any valid JSON string. 11 | * 12 | * If you want to use compression when saving to file / stream / B64 you have to include 13 | * SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) in your project and 14 | * define "USE_SharpZipLib" at the top of the file 15 | * 16 | * Written by Bunny83 17 | * 2012-06-09 18 | * 19 | * Modified by oPless, 2014-09-21 to round-trip properly 20 | * 21 | * Features / attributes: 22 | * - provides strongly typed node classes and lists / dictionaries 23 | * - provides easy access to class members / array items / data values 24 | * - the parser ignores data types. Each value is a string. 25 | * - only double quotes (") are used for quoting strings. 26 | * - values and names are not restricted to quoted strings. They simply add up and are trimmed. 27 | * - There are only 3 types: arrays(JSONArray), objects(JSONClass) and values(JSONData) 28 | * - provides "casting" properties to easily convert to / from those types: 29 | * int / float / double / bool 30 | * - provides a common interface for each node so no explicit casting is required. 31 | * - the parser try to avoid errors, but if malformed JSON is parsed the result is undefined 32 | * 33 | * 34 | * 2012-12-17 Update: 35 | * - Added internal JSONLazyCreator class which simplifies the construction of a JSON tree 36 | * Now you can simple reference any item that doesn't exist yet and it will return a JSONLazyCreator 37 | * The class determines the required type by it's further use, creates the type and removes itself. 38 | * - Added binary serialization / deserialization. 39 | * - Added support for BZip2 zipped binary format. Requires the SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) 40 | * The usage of the SharpZipLib library can be disabled by removing or commenting out the USE_SharpZipLib define at the top 41 | * - The serializer uses different types when it comes to store the values. Since my data values 42 | * are all of type string, the serializer will "try" which format fits best. The order is: int, float, double, bool, string. 43 | * It's not the most efficient way but for a moderate amount of data it should work on all platforms. 44 | * 45 | * * * * */ 46 | using System; 47 | using System.Collections; 48 | using System.Collections.Generic; 49 | using System.Linq; 50 | using UnityEngine; 51 | 52 | 53 | namespace SimpleJSON 54 | { 55 | public enum JSONBinaryTag 56 | { 57 | Array = 1, 58 | Class = 2, 59 | Value = 3, 60 | IntValue = 4, 61 | DoubleValue = 5, 62 | BoolValue = 6, 63 | FloatValue = 7, 64 | } 65 | 66 | public abstract class JSONNode 67 | { 68 | #region common interface 69 | 70 | public virtual void Add (string aKey, JSONNode aItem) 71 | { 72 | } 73 | 74 | public virtual JSONNode this [int aIndex] { get { return null; } set { } } 75 | 76 | public virtual JSONNode this [string aKey] { get { return null; } set { } } 77 | 78 | public virtual string Value { get { return ""; } set { } } 79 | 80 | public virtual int Count { get { return 0; } } 81 | 82 | public virtual void Add (JSONNode aItem) 83 | { 84 | Add ("", aItem); 85 | } 86 | 87 | public virtual JSONNode Remove (string aKey) 88 | { 89 | return null; 90 | } 91 | 92 | public virtual JSONNode Remove (int aIndex) 93 | { 94 | return null; 95 | } 96 | 97 | public virtual JSONNode Remove (JSONNode aNode) 98 | { 99 | return aNode; 100 | } 101 | 102 | public virtual IEnumerable Children 103 | { 104 | get { 105 | yield break; 106 | } 107 | } 108 | 109 | public IEnumerable DeepChildren 110 | { 111 | get { 112 | foreach (var C in Children) 113 | foreach (var D in C.DeepChildren) 114 | yield return D; 115 | } 116 | } 117 | 118 | public override string ToString () 119 | { 120 | return "JSONNode"; 121 | } 122 | 123 | public virtual string ToString (string aPrefix) 124 | { 125 | return "JSONNode"; 126 | } 127 | 128 | public abstract string ToJSON (int prefix); 129 | 130 | #endregion common interface 131 | 132 | #region typecasting properties 133 | 134 | public virtual JSONBinaryTag Tag { get; set; } 135 | 136 | public virtual int AsInt 137 | { 138 | get { 139 | int v = 0; 140 | if (int.TryParse (Value, out v)) 141 | return v; 142 | return 0; 143 | } 144 | set { 145 | Value = value.ToString (); 146 | Tag = JSONBinaryTag.IntValue; 147 | } 148 | } 149 | 150 | public virtual float AsFloat 151 | { 152 | get { 153 | float v = 0.0f; 154 | if (float.TryParse (Value, out v)) 155 | return v; 156 | return 0.0f; 157 | } 158 | set { 159 | Value = value.ToString (); 160 | Tag = JSONBinaryTag.FloatValue; 161 | } 162 | } 163 | 164 | public virtual double AsDouble 165 | { 166 | get { 167 | double v = 0.0; 168 | if (double.TryParse (Value, out v)) 169 | return v; 170 | return 0.0; 171 | } 172 | set { 173 | Value = value.ToString (); 174 | Tag = JSONBinaryTag.DoubleValue; 175 | 176 | } 177 | } 178 | 179 | public virtual bool AsBool 180 | { 181 | get { 182 | bool v = false; 183 | if (bool.TryParse (Value, out v)) 184 | return v; 185 | return !string.IsNullOrEmpty (Value); 186 | } 187 | set { 188 | Value = (value) ? "true" : "false"; 189 | Tag = JSONBinaryTag.BoolValue; 190 | 191 | } 192 | } 193 | 194 | public virtual JSONArray AsArray 195 | { 196 | get { 197 | return this as JSONArray; 198 | } 199 | } 200 | 201 | public virtual JSONClass AsObject 202 | { 203 | get { 204 | return this as JSONClass; 205 | } 206 | } 207 | 208 | 209 | #endregion typecasting properties 210 | 211 | #region operators 212 | 213 | public static implicit operator JSONNode (string s) 214 | { 215 | return new JSONData (s); 216 | } 217 | 218 | public static implicit operator string (JSONNode d) 219 | { 220 | return (d == null) ? null : d.Value; 221 | } 222 | 223 | public static bool operator == (JSONNode a, object b) 224 | { 225 | if (b == null && a is JSONLazyCreator) 226 | return true; 227 | return System.Object.ReferenceEquals (a, b); 228 | } 229 | 230 | public static bool operator != (JSONNode a, object b) 231 | { 232 | return !(a == b); 233 | } 234 | 235 | public override bool Equals (object obj) 236 | { 237 | return System.Object.ReferenceEquals (this, obj); 238 | } 239 | 240 | public override int GetHashCode () 241 | { 242 | return base.GetHashCode (); 243 | } 244 | 245 | 246 | #endregion operators 247 | 248 | internal static string Escape (string aText) 249 | { 250 | string result = ""; 251 | foreach (char c in aText) { 252 | switch (c) { 253 | case '\\': 254 | result += "\\\\"; 255 | break; 256 | case '\"': 257 | result += "\\\""; 258 | break; 259 | case '\n': 260 | result += "\\n"; 261 | break; 262 | case '\r': 263 | result += "\\r"; 264 | break; 265 | case '\t': 266 | result += "\\t"; 267 | break; 268 | case '\b': 269 | result += "\\b"; 270 | break; 271 | case '\f': 272 | result += "\\f"; 273 | break; 274 | default : 275 | result += c; 276 | break; 277 | } 278 | } 279 | return result; 280 | } 281 | 282 | static JSONData Numberize (string token) 283 | { 284 | bool flag = false; 285 | int integer = 0; 286 | double real = 0; 287 | 288 | if (int.TryParse (token, out integer)) { 289 | return new JSONData (integer); 290 | } 291 | 292 | if (double.TryParse (token, out real)) { 293 | return new JSONData (real); 294 | } 295 | 296 | if (bool.TryParse (token, out flag)) { 297 | return new JSONData (flag); 298 | } 299 | 300 | throw new NotImplementedException (token); 301 | } 302 | 303 | static void AddElement (JSONNode ctx, string token, string tokenName, bool tokenIsString) 304 | { 305 | if (tokenIsString) { 306 | if (ctx is JSONArray) 307 | ctx.Add (token); 308 | else 309 | ctx.Add (tokenName, token); // assume dictionary/object 310 | } else { 311 | JSONData number = Numberize (token); 312 | if (ctx is JSONArray) 313 | ctx.Add (number); 314 | else 315 | ctx.Add (tokenName, number); 316 | 317 | } 318 | } 319 | 320 | public static JSONNode Parse (string aJSON) 321 | { 322 | Stack stack = new Stack (); 323 | JSONNode ctx = null; 324 | int i = 0; 325 | string Token = ""; 326 | string TokenName = ""; 327 | bool QuoteMode = false; 328 | bool TokenIsString = false; 329 | while (i < aJSON.Length) { 330 | switch (aJSON [i]) { 331 | case '{': 332 | if (QuoteMode) { 333 | Token += aJSON [i]; 334 | break; 335 | } 336 | stack.Push (new JSONClass ()); 337 | if (ctx != null) { 338 | TokenName = TokenName.Trim (); 339 | if (ctx is JSONArray) 340 | ctx.Add (stack.Peek ()); 341 | else if (TokenName != "") 342 | ctx.Add (TokenName, stack.Peek ()); 343 | } 344 | TokenName = ""; 345 | Token = ""; 346 | ctx = stack.Peek (); 347 | break; 348 | 349 | case '[': 350 | if (QuoteMode) { 351 | Token += aJSON [i]; 352 | break; 353 | } 354 | 355 | stack.Push (new JSONArray ()); 356 | if (ctx != null) { 357 | TokenName = TokenName.Trim (); 358 | 359 | if (ctx is JSONArray) 360 | ctx.Add (stack.Peek ()); 361 | else if (TokenName != "") 362 | ctx.Add (TokenName, stack.Peek ()); 363 | } 364 | TokenName = ""; 365 | Token = ""; 366 | ctx = stack.Peek (); 367 | break; 368 | 369 | case '}': 370 | case ']': 371 | if (QuoteMode) { 372 | Token += aJSON [i]; 373 | break; 374 | } 375 | if (stack.Count == 0) 376 | throw new Exception ("JSON Parse: Too many closing brackets"); 377 | 378 | stack.Pop (); 379 | if (Token != "") { 380 | TokenName = TokenName.Trim (); 381 | /* 382 | if (ctx is JSONArray) 383 | ctx.Add (Token); 384 | else if (TokenName != "") 385 | ctx.Add (TokenName, Token); 386 | */ 387 | AddElement (ctx, Token, TokenName, TokenIsString); 388 | TokenIsString = false; 389 | } 390 | TokenName = ""; 391 | Token = ""; 392 | if (stack.Count > 0) 393 | ctx = stack.Peek (); 394 | break; 395 | 396 | case ':': 397 | if (QuoteMode) { 398 | Token += aJSON [i]; 399 | break; 400 | } 401 | TokenName = Token; 402 | Token = ""; 403 | TokenIsString = false; 404 | break; 405 | 406 | case '"': 407 | QuoteMode ^= true; 408 | TokenIsString = QuoteMode == true ? true : TokenIsString; 409 | break; 410 | 411 | case ',': 412 | if (QuoteMode) { 413 | Token += aJSON [i]; 414 | break; 415 | } 416 | if (Token != "") { 417 | /* 418 | if (ctx is JSONArray) { 419 | ctx.Add (Token); 420 | } else if (TokenName != "") { 421 | ctx.Add (TokenName, Token); 422 | } 423 | */ 424 | AddElement (ctx, Token, TokenName, TokenIsString); 425 | TokenIsString = false; 426 | 427 | } 428 | TokenName = ""; 429 | Token = ""; 430 | TokenIsString = false; 431 | break; 432 | 433 | case '\r': 434 | case '\n': 435 | break; 436 | 437 | case ' ': 438 | case '\t': 439 | if (QuoteMode) 440 | Token += aJSON [i]; 441 | break; 442 | 443 | case '\\': 444 | ++i; 445 | if (QuoteMode) { 446 | char C = aJSON [i]; 447 | switch (C) { 448 | case 't': 449 | Token += '\t'; 450 | break; 451 | case 'r': 452 | Token += '\r'; 453 | break; 454 | case 'n': 455 | Token += '\n'; 456 | break; 457 | case 'b': 458 | Token += '\b'; 459 | break; 460 | case 'f': 461 | Token += '\f'; 462 | break; 463 | case 'u': 464 | { 465 | string s = aJSON.Substring (i + 1, 4); 466 | Token += (char)int.Parse ( 467 | s, 468 | System.Globalization.NumberStyles.AllowHexSpecifier); 469 | i += 4; 470 | break; 471 | } 472 | default : 473 | Token += C; 474 | break; 475 | } 476 | } 477 | break; 478 | 479 | default: 480 | Token += aJSON [i]; 481 | break; 482 | } 483 | ++i; 484 | } 485 | if (QuoteMode) { 486 | throw new Exception ("JSON Parse: Quotation marks seems to be messed up."); 487 | } 488 | return ctx; 489 | } 490 | 491 | public virtual void Serialize (System.IO.BinaryWriter aWriter) 492 | { 493 | } 494 | 495 | public void SaveToStream (System.IO.Stream aData) 496 | { 497 | var W = new System.IO.BinaryWriter (aData); 498 | Serialize (W); 499 | } 500 | 501 | #if USE_SharpZipLib 502 | public void SaveToCompressedStream(System.IO.Stream aData) 503 | { 504 | using (var gzipOut = new ICSharpCode.SharpZipLib.BZip2.BZip2OutputStream(aData)) 505 | { 506 | gzipOut.IsStreamOwner = false; 507 | SaveToStream(gzipOut); 508 | gzipOut.Close(); 509 | } 510 | } 511 | 512 | public void SaveToCompressedFile(string aFileName) 513 | { 514 | 515 | #if USE_FileIO 516 | System.IO.Directory.CreateDirectory((new System.IO.FileInfo(aFileName)).Directory.FullName); 517 | using(var F = System.IO.File.OpenWrite(aFileName)) 518 | { 519 | SaveToCompressedStream(F); 520 | } 521 | 522 | #else 523 | throw new Exception("Can't use File IO stuff in webplayer"); 524 | #endif 525 | } 526 | public string SaveToCompressedBase64() 527 | { 528 | using (var stream = new System.IO.MemoryStream()) 529 | { 530 | SaveToCompressedStream(stream); 531 | stream.Position = 0; 532 | return System.Convert.ToBase64String(stream.ToArray()); 533 | } 534 | } 535 | 536 | #else 537 | public void SaveToCompressedStream (System.IO.Stream aData) 538 | { 539 | throw new Exception ("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 540 | } 541 | 542 | public void SaveToCompressedFile (string aFileName) 543 | { 544 | throw new Exception ("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 545 | } 546 | 547 | public string SaveToCompressedBase64 () 548 | { 549 | throw new Exception ("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 550 | } 551 | #endif 552 | 553 | public void SaveToFile (string aFileName) 554 | { 555 | #if USE_FileIO 556 | System.IO.Directory.CreateDirectory ((new System.IO.FileInfo (aFileName)).Directory.FullName); 557 | using (var F = System.IO.File.OpenWrite (aFileName)) { 558 | SaveToStream (F); 559 | } 560 | #else 561 | throw new Exception ("Can't use File IO stuff in webplayer"); 562 | #endif 563 | } 564 | 565 | public string SaveToBase64 () 566 | { 567 | using (var stream = new System.IO.MemoryStream ()) { 568 | SaveToStream (stream); 569 | stream.Position = 0; 570 | return System.Convert.ToBase64String (stream.ToArray ()); 571 | } 572 | } 573 | 574 | public static JSONNode Deserialize (System.IO.BinaryReader aReader) 575 | { 576 | JSONBinaryTag type = (JSONBinaryTag)aReader.ReadByte (); 577 | switch (type) { 578 | case JSONBinaryTag.Array: 579 | { 580 | int count = aReader.ReadInt32 (); 581 | JSONArray tmp = new JSONArray (); 582 | for (int i = 0; i < count; i++) 583 | tmp.Add (Deserialize (aReader)); 584 | return tmp; 585 | } 586 | case JSONBinaryTag.Class: 587 | { 588 | int count = aReader.ReadInt32 (); 589 | JSONClass tmp = new JSONClass (); 590 | for (int i = 0; i < count; i++) { 591 | string key = aReader.ReadString (); 592 | var val = Deserialize (aReader); 593 | tmp.Add (key, val); 594 | } 595 | return tmp; 596 | } 597 | case JSONBinaryTag.Value: 598 | { 599 | return new JSONData (aReader.ReadString ()); 600 | } 601 | case JSONBinaryTag.IntValue: 602 | { 603 | return new JSONData (aReader.ReadInt32 ()); 604 | } 605 | case JSONBinaryTag.DoubleValue: 606 | { 607 | return new JSONData (aReader.ReadDouble ()); 608 | } 609 | case JSONBinaryTag.BoolValue: 610 | { 611 | return new JSONData (aReader.ReadBoolean ()); 612 | } 613 | case JSONBinaryTag.FloatValue: 614 | { 615 | return new JSONData (aReader.ReadSingle ()); 616 | } 617 | 618 | default: 619 | { 620 | throw new Exception ("Error deserializing JSON. Unknown tag: " + type); 621 | } 622 | } 623 | } 624 | 625 | #if USE_SharpZipLib 626 | public static JSONNode LoadFromCompressedStream(System.IO.Stream aData) 627 | { 628 | var zin = new ICSharpCode.SharpZipLib.BZip2.BZip2InputStream(aData); 629 | return LoadFromStream(zin); 630 | } 631 | public static JSONNode LoadFromCompressedFile(string aFileName) 632 | { 633 | #if USE_FileIO 634 | using(var F = System.IO.File.OpenRead(aFileName)) 635 | { 636 | return LoadFromCompressedStream(F); 637 | } 638 | #else 639 | throw new Exception("Can't use File IO stuff in webplayer"); 640 | #endif 641 | } 642 | public static JSONNode LoadFromCompressedBase64(string aBase64) 643 | { 644 | var tmp = System.Convert.FromBase64String(aBase64); 645 | var stream = new System.IO.MemoryStream(tmp); 646 | stream.Position = 0; 647 | return LoadFromCompressedStream(stream); 648 | } 649 | #else 650 | public static JSONNode LoadFromCompressedFile (string aFileName) 651 | { 652 | throw new Exception ("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 653 | } 654 | 655 | public static JSONNode LoadFromCompressedStream (System.IO.Stream aData) 656 | { 657 | throw new Exception ("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 658 | } 659 | 660 | public static JSONNode LoadFromCompressedBase64 (string aBase64) 661 | { 662 | throw new Exception ("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 663 | } 664 | #endif 665 | 666 | public static JSONNode LoadFromStream (System.IO.Stream aData) 667 | { 668 | using (var R = new System.IO.BinaryReader (aData)) { 669 | return Deserialize (R); 670 | } 671 | } 672 | 673 | public static JSONNode LoadFromFile (string aFileName) 674 | { 675 | #if USE_FileIO 676 | using (var F = System.IO.File.OpenRead (aFileName)) { 677 | return LoadFromStream (F); 678 | } 679 | #else 680 | throw new Exception ("Can't use File IO stuff in webplayer"); 681 | #endif 682 | } 683 | 684 | public static JSONNode LoadFromBase64 (string aBase64) 685 | { 686 | var tmp = System.Convert.FromBase64String (aBase64); 687 | var stream = new System.IO.MemoryStream (tmp); 688 | stream.Position = 0; 689 | return LoadFromStream (stream); 690 | } 691 | } 692 | // End of JSONNode 693 | 694 | public class JSONArray : JSONNode, IEnumerable 695 | { 696 | private List m_List = new List (); 697 | 698 | public override JSONNode this [int aIndex] 699 | { 700 | get { 701 | if (aIndex < 0 || aIndex >= m_List.Count) 702 | return new JSONLazyCreator (this); 703 | return m_List [aIndex]; 704 | } 705 | set { 706 | if (aIndex < 0 || aIndex >= m_List.Count) 707 | m_List.Add (value); 708 | else 709 | m_List [aIndex] = value; 710 | } 711 | } 712 | 713 | public override JSONNode this [string aKey] 714 | { 715 | get{ return new JSONLazyCreator (this); } 716 | set{ m_List.Add (value); } 717 | } 718 | 719 | public override int Count 720 | { 721 | get { return m_List.Count; } 722 | } 723 | 724 | public override void Add (string aKey, JSONNode aItem) 725 | { 726 | m_List.Add (aItem); 727 | } 728 | 729 | public override JSONNode Remove (int aIndex) 730 | { 731 | if (aIndex < 0 || aIndex >= m_List.Count) 732 | return null; 733 | JSONNode tmp = m_List [aIndex]; 734 | m_List.RemoveAt (aIndex); 735 | return tmp; 736 | } 737 | 738 | public override JSONNode Remove (JSONNode aNode) 739 | { 740 | m_List.Remove (aNode); 741 | return aNode; 742 | } 743 | 744 | public override IEnumerable Children 745 | { 746 | get { 747 | foreach (JSONNode N in m_List) 748 | yield return N; 749 | } 750 | } 751 | 752 | public IEnumerator GetEnumerator () 753 | { 754 | foreach (JSONNode N in m_List) 755 | yield return N; 756 | } 757 | 758 | public override string ToString () 759 | { 760 | string result = "[ "; 761 | foreach (JSONNode N in m_List) { 762 | if (result.Length > 2) 763 | result += ", "; 764 | result += N.ToString (); 765 | } 766 | result += " ]"; 767 | return result; 768 | } 769 | 770 | public override string ToString (string aPrefix) 771 | { 772 | string result = "[ "; 773 | foreach (JSONNode N in m_List) { 774 | if (result.Length > 3) 775 | result += ", "; 776 | result += "\n" + aPrefix + " "; 777 | result += N.ToString (aPrefix + " "); 778 | } 779 | result += "\n" + aPrefix + "]"; 780 | return result; 781 | } 782 | 783 | public override string ToJSON (int prefix) 784 | { 785 | string s = new string (' ', (prefix + 1) * 2); 786 | string ret = "[ "; 787 | foreach (JSONNode n in m_List) { 788 | if (ret.Length > 3) 789 | ret += ", "; 790 | ret += "\n" + s; 791 | ret += n.ToJSON (prefix + 1); 792 | 793 | } 794 | ret += "\n" + s + "]"; 795 | return ret; 796 | } 797 | 798 | public override void Serialize (System.IO.BinaryWriter aWriter) 799 | { 800 | aWriter.Write ((byte)JSONBinaryTag.Array); 801 | aWriter.Write (m_List.Count); 802 | for (int i = 0; i < m_List.Count; i++) { 803 | m_List [i].Serialize (aWriter); 804 | } 805 | } 806 | } 807 | // End of JSONArray 808 | 809 | public class JSONClass : JSONNode, IEnumerable 810 | { 811 | private Dictionary m_Dict = new Dictionary (); 812 | 813 | public Dictionary Dict { 814 | get { 815 | return m_Dict; 816 | } 817 | } 818 | 819 | public override JSONNode this [string aKey] 820 | { 821 | get { 822 | if (m_Dict.ContainsKey (aKey)) 823 | return m_Dict [aKey]; 824 | else 825 | return new JSONLazyCreator (this, aKey); 826 | } 827 | set { 828 | if (m_Dict.ContainsKey (aKey)) 829 | m_Dict [aKey] = value; 830 | else 831 | m_Dict.Add (aKey, value); 832 | } 833 | } 834 | 835 | public override JSONNode this [int aIndex] 836 | { 837 | get { 838 | if (aIndex < 0 || aIndex >= m_Dict.Count) 839 | return null; 840 | return m_Dict.ElementAt (aIndex).Value; 841 | } 842 | set { 843 | if (aIndex < 0 || aIndex >= m_Dict.Count) 844 | return; 845 | string key = m_Dict.ElementAt (aIndex).Key; 846 | m_Dict [key] = value; 847 | } 848 | } 849 | 850 | public override int Count 851 | { 852 | get { return m_Dict.Count; } 853 | } 854 | 855 | 856 | public override void Add (string aKey, JSONNode aItem) 857 | { 858 | if (!string.IsNullOrEmpty (aKey)) { 859 | if (m_Dict.ContainsKey (aKey)) 860 | m_Dict [aKey] = aItem; 861 | else 862 | m_Dict.Add (aKey, aItem); 863 | } else 864 | m_Dict.Add (Guid.NewGuid ().ToString (), aItem); 865 | } 866 | 867 | public override JSONNode Remove (string aKey) 868 | { 869 | if (!m_Dict.ContainsKey (aKey)) 870 | return null; 871 | JSONNode tmp = m_Dict [aKey]; 872 | m_Dict.Remove (aKey); 873 | return tmp; 874 | } 875 | 876 | public override JSONNode Remove (int aIndex) 877 | { 878 | if (aIndex < 0 || aIndex >= m_Dict.Count) 879 | return null; 880 | var item = m_Dict.ElementAt (aIndex); 881 | m_Dict.Remove (item.Key); 882 | return item.Value; 883 | } 884 | 885 | public override JSONNode Remove (JSONNode aNode) 886 | { 887 | try { 888 | var item = m_Dict.Where (k => k.Value == aNode).First (); 889 | m_Dict.Remove (item.Key); 890 | return aNode; 891 | } catch { 892 | return null; 893 | } 894 | } 895 | 896 | public override IEnumerable Children 897 | { 898 | get { 899 | foreach (KeyValuePair N in m_Dict) 900 | yield return N.Value; 901 | } 902 | } 903 | 904 | public IEnumerator GetEnumerator () 905 | { 906 | foreach (KeyValuePair N in m_Dict) 907 | yield return N; 908 | } 909 | 910 | public override string ToString () 911 | { 912 | string result = "{"; 913 | foreach (KeyValuePair N in m_Dict) { 914 | if (result.Length > 2) 915 | result += ", "; 916 | result += "\"" + Escape (N.Key) + "\":" + N.Value.ToString (); 917 | } 918 | result += "}"; 919 | return result; 920 | } 921 | 922 | public override string ToString (string aPrefix) 923 | { 924 | string result = "{ "; 925 | foreach (KeyValuePair N in m_Dict) { 926 | if (result.Length > 3) 927 | result += ", "; 928 | result += "\n" + aPrefix + " "; 929 | result += "\"" + Escape (N.Key) + "\" : " + N.Value.ToString (aPrefix + " "); 930 | } 931 | result += "\n" + aPrefix + "}"; 932 | return result; 933 | } 934 | 935 | public override string ToJSON (int prefix) 936 | { 937 | string s = new string (' ', (prefix + 1) * 2); 938 | string ret = "{ "; 939 | foreach (KeyValuePair n in m_Dict) { 940 | if (ret.Length > 3) 941 | ret += ", "; 942 | ret += "\n" + s; 943 | ret += string.Format ("\"{0}\": {1}", n.Key, n.Value.ToJSON (prefix + 1)); 944 | } 945 | ret += "\n" + s + "}"; 946 | return ret; 947 | } 948 | 949 | public override void Serialize (System.IO.BinaryWriter aWriter) 950 | { 951 | aWriter.Write ((byte)JSONBinaryTag.Class); 952 | aWriter.Write (m_Dict.Count); 953 | foreach (string K in m_Dict.Keys) { 954 | aWriter.Write (K); 955 | m_Dict [K].Serialize (aWriter); 956 | } 957 | } 958 | } 959 | // End of JSONClass 960 | 961 | public class JSONData : JSONNode 962 | { 963 | private string m_Data; 964 | 965 | 966 | public override string Value 967 | { 968 | get { return m_Data; } 969 | set { 970 | m_Data = value; 971 | Tag = JSONBinaryTag.Value; 972 | } 973 | } 974 | 975 | public JSONData (string aData) 976 | { 977 | m_Data = aData; 978 | Tag = JSONBinaryTag.Value; 979 | } 980 | 981 | public JSONData (float aData) 982 | { 983 | AsFloat = aData; 984 | } 985 | 986 | public JSONData (double aData) 987 | { 988 | AsDouble = aData; 989 | } 990 | 991 | public JSONData (bool aData) 992 | { 993 | AsBool = aData; 994 | } 995 | 996 | public JSONData (int aData) 997 | { 998 | AsInt = aData; 999 | } 1000 | 1001 | public override string ToString () 1002 | { 1003 | return "\"" + Escape (m_Data) + "\""; 1004 | } 1005 | 1006 | public override string ToString (string aPrefix) 1007 | { 1008 | return "\"" + Escape (m_Data) + "\""; 1009 | } 1010 | 1011 | public override string ToJSON (int prefix) 1012 | { 1013 | switch (Tag) { 1014 | case JSONBinaryTag.DoubleValue: 1015 | case JSONBinaryTag.FloatValue: 1016 | case JSONBinaryTag.IntValue: 1017 | return m_Data; 1018 | case JSONBinaryTag.Value: 1019 | return string.Format ("\"{0}\"", Escape (m_Data)); 1020 | default: 1021 | throw new NotSupportedException ("This shouldn't be here: " + Tag.ToString ()); 1022 | } 1023 | } 1024 | 1025 | public override void Serialize (System.IO.BinaryWriter aWriter) 1026 | { 1027 | var tmp = new JSONData (""); 1028 | 1029 | tmp.AsInt = AsInt; 1030 | if (tmp.m_Data == this.m_Data) { 1031 | aWriter.Write ((byte)JSONBinaryTag.IntValue); 1032 | aWriter.Write (AsInt); 1033 | return; 1034 | } 1035 | tmp.AsFloat = AsFloat; 1036 | if (tmp.m_Data == this.m_Data) { 1037 | aWriter.Write ((byte)JSONBinaryTag.FloatValue); 1038 | aWriter.Write (AsFloat); 1039 | return; 1040 | } 1041 | tmp.AsDouble = AsDouble; 1042 | if (tmp.m_Data == this.m_Data) { 1043 | aWriter.Write ((byte)JSONBinaryTag.DoubleValue); 1044 | aWriter.Write (AsDouble); 1045 | return; 1046 | } 1047 | 1048 | tmp.AsBool = AsBool; 1049 | if (tmp.m_Data == this.m_Data) { 1050 | aWriter.Write ((byte)JSONBinaryTag.BoolValue); 1051 | aWriter.Write (AsBool); 1052 | return; 1053 | } 1054 | aWriter.Write ((byte)JSONBinaryTag.Value); 1055 | aWriter.Write (m_Data); 1056 | } 1057 | } 1058 | // End of JSONData 1059 | 1060 | internal class JSONLazyCreator : JSONNode 1061 | { 1062 | private JSONNode m_Node = null; 1063 | private string m_Key = null; 1064 | 1065 | public JSONLazyCreator (JSONNode aNode) 1066 | { 1067 | m_Node = aNode; 1068 | m_Key = null; 1069 | } 1070 | 1071 | public JSONLazyCreator (JSONNode aNode, string aKey) 1072 | { 1073 | m_Node = aNode; 1074 | m_Key = aKey; 1075 | } 1076 | 1077 | private void Set (JSONNode aVal) 1078 | { 1079 | if (m_Key == null) { 1080 | m_Node.Add (aVal); 1081 | } else { 1082 | m_Node.Add (m_Key, aVal); 1083 | } 1084 | m_Node = null; // Be GC friendly. 1085 | } 1086 | 1087 | public override JSONNode this [int aIndex] 1088 | { 1089 | get { 1090 | return new JSONLazyCreator (this); 1091 | } 1092 | set { 1093 | var tmp = new JSONArray (); 1094 | tmp.Add (value); 1095 | Set (tmp); 1096 | } 1097 | } 1098 | 1099 | public override JSONNode this [string aKey] 1100 | { 1101 | get { 1102 | return new JSONLazyCreator (this, aKey); 1103 | } 1104 | set { 1105 | var tmp = new JSONClass (); 1106 | tmp.Add (aKey, value); 1107 | Set (tmp); 1108 | } 1109 | } 1110 | 1111 | public override void Add (JSONNode aItem) 1112 | { 1113 | var tmp = new JSONArray (); 1114 | tmp.Add (aItem); 1115 | Set (tmp); 1116 | } 1117 | 1118 | public override void Add (string aKey, JSONNode aItem) 1119 | { 1120 | var tmp = new JSONClass (); 1121 | tmp.Add (aKey, aItem); 1122 | Set (tmp); 1123 | } 1124 | 1125 | public static bool operator == (JSONLazyCreator a, object b) 1126 | { 1127 | if (b == null) 1128 | return true; 1129 | return System.Object.ReferenceEquals (a, b); 1130 | } 1131 | 1132 | public static bool operator != (JSONLazyCreator a, object b) 1133 | { 1134 | return !(a == b); 1135 | } 1136 | 1137 | public override bool Equals (object obj) 1138 | { 1139 | if (obj == null) 1140 | return true; 1141 | return System.Object.ReferenceEquals (this, obj); 1142 | } 1143 | 1144 | public override int GetHashCode () 1145 | { 1146 | return base.GetHashCode (); 1147 | } 1148 | 1149 | public override string ToString () 1150 | { 1151 | return ""; 1152 | } 1153 | 1154 | public override string ToString (string aPrefix) 1155 | { 1156 | return ""; 1157 | } 1158 | 1159 | public override string ToJSON (int prefix) 1160 | { 1161 | return ""; 1162 | } 1163 | 1164 | public override int AsInt 1165 | { 1166 | get { 1167 | JSONData tmp = new JSONData (0); 1168 | Set (tmp); 1169 | return 0; 1170 | } 1171 | set { 1172 | JSONData tmp = new JSONData (value); 1173 | Set (tmp); 1174 | } 1175 | } 1176 | 1177 | public override float AsFloat 1178 | { 1179 | get { 1180 | JSONData tmp = new JSONData (0.0f); 1181 | Set (tmp); 1182 | return 0.0f; 1183 | } 1184 | set { 1185 | JSONData tmp = new JSONData (value); 1186 | Set (tmp); 1187 | } 1188 | } 1189 | 1190 | public override double AsDouble 1191 | { 1192 | get { 1193 | JSONData tmp = new JSONData (0.0); 1194 | Set (tmp); 1195 | return 0.0; 1196 | } 1197 | set { 1198 | JSONData tmp = new JSONData (value); 1199 | Set (tmp); 1200 | } 1201 | } 1202 | 1203 | public override bool AsBool 1204 | { 1205 | get { 1206 | JSONData tmp = new JSONData (false); 1207 | Set (tmp); 1208 | return false; 1209 | } 1210 | set { 1211 | JSONData tmp = new JSONData (value); 1212 | Set (tmp); 1213 | } 1214 | } 1215 | 1216 | public override JSONArray AsArray 1217 | { 1218 | get { 1219 | JSONArray tmp = new JSONArray (); 1220 | Set (tmp); 1221 | return tmp; 1222 | } 1223 | } 1224 | 1225 | public override JSONClass AsObject 1226 | { 1227 | get { 1228 | JSONClass tmp = new JSONClass (); 1229 | Set (tmp); 1230 | return tmp; 1231 | } 1232 | } 1233 | } 1234 | // End of JSONLazyCreator 1235 | 1236 | public static class JSON 1237 | { 1238 | public static JSONNode Parse (string aJSON) 1239 | { 1240 | return JSONNode.Parse (aJSON); 1241 | } 1242 | } 1243 | } --------------------------------------------------------------------------------