370 lines
11 KiB
C#
370 lines
11 KiB
C#
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using UnityEditor;
|
||
|
using UnityEditor.AnimatedValues;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.SceneManagement;
|
||
|
using UnityGLTF;
|
||
|
|
||
|
namespace Plattar {
|
||
|
public class Exporter : EditorWindow {
|
||
|
|
||
|
GameObject selectedObject;
|
||
|
static Texture logo;
|
||
|
static readonly List<int> pivotCheck = new List<int>();
|
||
|
|
||
|
[MenuItem("Plattar/GLTF Exporter")]
|
||
|
static void Init() {
|
||
|
RefreshLogo();
|
||
|
|
||
|
Type inspectorType = Type.GetType("UnityEditor.InspectorWindow,UnityEditor.dll");
|
||
|
EditorWindow window = EditorWindow.GetWindow<Exporter>(new Type[] {inspectorType});
|
||
|
window.Show();
|
||
|
}
|
||
|
|
||
|
static void RefreshLogo() {
|
||
|
if (logo == null) {
|
||
|
logo = (Texture2D) AssetDatabase.LoadAssetAtPath("Assets/PlattarExporter/Plattar/Editor/ExporterHeader.png", typeof(Texture2D));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnEnable() { }
|
||
|
|
||
|
void OnGUI() {
|
||
|
RefreshLogo();
|
||
|
|
||
|
GUILayout.BeginHorizontal();
|
||
|
GUILayout.FlexibleSpace();
|
||
|
GUILayout.Label(logo, GUILayout.Width(150), GUILayout.Height(150));
|
||
|
GUILayout.FlexibleSpace();
|
||
|
GUILayout.EndHorizontal();
|
||
|
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
|
||
|
var foundGrids = GameObject.FindObjectsOfType<AlignmentScript>();
|
||
|
|
||
|
if (foundGrids != null && foundGrids.Length > 0) {
|
||
|
if (GUILayout.Button("Hide Alignment Grid")) {
|
||
|
for (int i = 0; i < foundGrids.Length; i++) {
|
||
|
if (foundGrids[i] != null && foundGrids[i].gameObject != null) {
|
||
|
GameObject.DestroyImmediate(foundGrids[i].gameObject);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (GUILayout.Button("Show Alignment Grid")) {
|
||
|
var grid = (GameObject) AssetDatabase.LoadAssetAtPath("Assets/PlattarExporter/Plattar/Alignment/AlignmentPlane.prefab", typeof(GameObject));
|
||
|
var obj = GameObject.Instantiate(grid);
|
||
|
|
||
|
obj.name = "Plattar Alignment Grid";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (GUILayout.Button("Reset Exporter Defaults")) {
|
||
|
GLTFTextureUtils.textureOption = GLTFTextureUtils.TextureExportOption.None;
|
||
|
GLTFTextureUtils.JPGQuality = 75;
|
||
|
GLTFUtils.boundsExportOption = GLTFUtils.BoundsExportOption.Default;
|
||
|
PlattarExporterOptions.ExportAnimations = true;
|
||
|
}
|
||
|
|
||
|
EditorGUILayout.EndVertical();
|
||
|
EditorGUILayout.Separator();
|
||
|
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
selectedObject = (GameObject) EditorGUILayout.ObjectField("Export Object", selectedObject, typeof(GameObject), true);
|
||
|
EditorGUILayout.EndVertical();
|
||
|
|
||
|
if (selectedObject == null) {
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
EditorGUILayout.HelpBox("You need to select a GameObject to continue", MessageType.Error);
|
||
|
EditorGUILayout.EndVertical();
|
||
|
} else {
|
||
|
string selectionName = selectedObject.name;
|
||
|
|
||
|
// ensure the name of our object is valid
|
||
|
if (string.IsNullOrEmpty(selectionName) || Regex.Matches(selectionName, @"[a-zA-Z]").Count <= 0) {
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
EditorGUILayout.HelpBox("Your Selected GameObject does not have a valid name", MessageType.Error);
|
||
|
EditorGUILayout.EndVertical();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var checkScripts = selectedObject.GetComponentsInChildren<AlignmentScript>();
|
||
|
|
||
|
if (checkScripts != null && checkScripts.Length > 0) {
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
EditorGUILayout.HelpBox("Your selected GameObject or its children contains an Alignment Grid", MessageType.Error);
|
||
|
EditorGUILayout.EndVertical();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var meshes = selectedObject.GetComponentsInChildren<MeshFilter>();
|
||
|
var skinnedMeshes = selectedObject.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||
|
|
||
|
if ((meshes == null || meshes.Length <= 0) && (skinnedMeshes == null || skinnedMeshes.Length <= 0)) {
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
EditorGUILayout.HelpBox("Your selected GameObject or its children have no Geometry", MessageType.Error);
|
||
|
EditorGUILayout.EndVertical();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EditorGUILayout.Separator();
|
||
|
|
||
|
ShowAnimationModifiersUI();
|
||
|
EditorGUILayout.Separator();
|
||
|
|
||
|
ShowMeshModifiersUI(selectedObject);
|
||
|
EditorGUILayout.Separator();
|
||
|
|
||
|
ShowTextureModifiersUI();
|
||
|
EditorGUILayout.Separator();
|
||
|
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
|
||
|
if (GUILayout.Button($"Export {selectionName} to GLTF")) {
|
||
|
if (GenerateGLTFZipped(selectedObject) != null) {
|
||
|
EditorUtility.DisplayDialog("Successful Export", "GLTF Exported and Zipped Successfully", "OK");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EditorGUILayout.EndVertical();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void ShowMeshModifiersUI(GameObject selectedObject) {
|
||
|
EditorGUILayout.BeginVertical("HelpBox");
|
||
|
|
||
|
EditorGUILayout.LabelField("Mesh Options");
|
||
|
|
||
|
bool hasAnim = SelectionHasAnimation(selectedObject);
|
||
|
|
||
|
if (!hasAnim) {
|
||
|
EditorGUILayout.HelpBox("Use to fix google scene-viewer hovering issues", MessageType.Info);
|
||
|
}
|
||
|
else {
|
||
|
EditorGUILayout.HelpBox("Pivot realignment is unavailable for animated objects", MessageType.Error);
|
||
|
}
|
||
|
|
||
|
EditorGUI.BeginDisabledGroup(hasAnim);
|
||
|
|
||
|
if (GUILayout.Button("Realign Pivot Center")) {
|
||
|
if (EditorUtility.DisplayDialog("Re-Align Mesh Pivots?", "Are you sure you want to re-align the pivot center? This operation will modify mesh data and cannot be reversed, continue?", "Yes", "Cancel")) {
|
||
|
CenterMesh(selectedObject);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EditorGUI.EndDisabledGroup();
|
||
|
|
||
|
EditorGUI.BeginDisabledGroup(!pivotCheck.Contains(selectedObject.GetInstanceID()));
|
||
|
|
||
|
// only support center-pivot objects for now
|
||
|
if (GUILayout.Button($"Pin to Alignment Grid")) {
|
||
|
PinToGrid(selectedObject);
|
||
|
}
|
||
|
|
||
|
EditorGUI.EndDisabledGroup();
|
||
|
|
||
|
GLTFUtils.boundsExportOption = (GLTFUtils.BoundsExportOption)EditorGUILayout.EnumPopup("Bounding Box Calculation", GLTFUtils.boundsExportOption);
|
||
|
|
||
|
EditorGUILayout.EndVertical();
|
||
|
}
|
||
|
|
||
|
private static void ShowTextureModifiersUI() {
|
||
|
EditorGUILayout.BeginVertical("HelpBox");
|
||
|
|
||
|
EditorGUILayout.LabelField("Texture Options");
|
||
|
|
||
|
GLTFTextureUtils.textureOption = (GLTFTextureUtils.TextureExportOption)EditorGUILayout.EnumPopup("Force Texture Conversion", GLTFTextureUtils.textureOption);
|
||
|
|
||
|
EditorGUI.BeginDisabledGroup(GLTFTextureUtils.textureOption == GLTFTextureUtils.TextureExportOption.PNG);
|
||
|
|
||
|
GLTFTextureUtils.JPGQuality = EditorGUILayout.IntSlider("JPG Texture Quality", GLTFTextureUtils.JPGQuality, 0, 100);
|
||
|
|
||
|
EditorGUI.EndDisabledGroup();
|
||
|
|
||
|
GLTFEditorExporter.materialExportOption = (GLTFEditorExporter.MaterialDoubleSidedExport)EditorGUILayout.EnumPopup("Material Side", GLTFEditorExporter.materialExportOption);
|
||
|
|
||
|
EditorGUILayout.EndVertical();
|
||
|
}
|
||
|
|
||
|
private static void ShowAnimationModifiersUI() {
|
||
|
EditorGUILayout.BeginVertical("HelpBox");
|
||
|
|
||
|
EditorGUILayout.LabelField("Animation Options");
|
||
|
|
||
|
PlattarExporterOptions.ExportAnimations = EditorGUILayout.Toggle("Export Animations", PlattarExporterOptions.ExportAnimations);
|
||
|
|
||
|
EditorGUILayout.EndVertical();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generate the GLTF file and zip it all up
|
||
|
*/
|
||
|
public static Tuple<string, string, string> GenerateGLTFZipped(GameObject selectedObject) {
|
||
|
var path = GenerateGLTF(selectedObject);
|
||
|
|
||
|
if (path == null) {
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
// otherwise, we need to zip up the entire directory
|
||
|
// and delete the original
|
||
|
PlattarExporterOptions.CompressDirectory(path.Item2, path.Item1 + "/" + path.Item3);
|
||
|
PlattarExporterOptions.DeleteDirectory(path.Item2);
|
||
|
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generate a non-zipped GLTF file with all folders etc
|
||
|
*/
|
||
|
public static Tuple<string, string, string> GenerateGLTF(GameObject selectedObject) {
|
||
|
string selectionName = selectedObject.name;
|
||
|
|
||
|
string fullpath = EditorUtility.SaveFilePanel("glTF Export Path", PlattarExporterOptions.LastEditorPath, selectionName, "zip");
|
||
|
PlattarExporterOptions.LastEditorPath = Path.GetDirectoryName(fullpath);
|
||
|
|
||
|
string selectedName = Path.GetFileNameWithoutExtension(fullpath);
|
||
|
|
||
|
var path = PlattarExporterOptions.LastEditorPath;
|
||
|
if (!string.IsNullOrEmpty(path)) {
|
||
|
var newpath = path + "/" + selectionName + "_export_gltf";
|
||
|
DirectoryInfo info = Directory.CreateDirectory(newpath);
|
||
|
|
||
|
if (info.Exists) {
|
||
|
if (PlattarExporterOptions.ExportAnimations == true) {
|
||
|
var exporter = new GLTFEditorExporter(new Transform[] { selectedObject.transform });
|
||
|
exporter.SaveGLTFandBin(newpath, selectionName);
|
||
|
}
|
||
|
else {
|
||
|
var exporter = new GLTFSceneExporter(new Transform[] { selectedObject.transform });
|
||
|
exporter.SaveGLTFandBin(newpath, selectionName);
|
||
|
}
|
||
|
|
||
|
return new Tuple<string, string, string>(path, newpath, selectedName);
|
||
|
}
|
||
|
|
||
|
EditorUtility.DisplayDialog("Failed Export", "GLTF Could not be exported, could not create export path", "OK");
|
||
|
} else {
|
||
|
EditorGUILayout.HelpBox("Failed to export since the path is invalid", MessageType.Error);
|
||
|
EditorUtility.DisplayDialog("Failed Export", "GLTF Could not be exported, invalid export path provided", "OK");
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public static void PinToGrid(GameObject root) {
|
||
|
MeshFilter[] filters = root.GetComponentsInChildren<MeshFilter>();
|
||
|
|
||
|
Bounds totalBounds = new Bounds();
|
||
|
|
||
|
int count = filters.Length;
|
||
|
|
||
|
// find the center pivot of ALL meshes
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
if (filters[i] != null) {
|
||
|
Mesh mesh = filters[i].sharedMesh;
|
||
|
|
||
|
if (mesh != null) {
|
||
|
Vector3[] positions = mesh.vertices;
|
||
|
int pCount = positions.Length;
|
||
|
|
||
|
for (int j = 0; j < pCount; j++) {
|
||
|
// ensure we are using the world position of the Transform
|
||
|
totalBounds.Encapsulate(filters[i].gameObject.transform.TransformPoint(positions[j]));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Vector3 position = root.gameObject.transform.position;
|
||
|
|
||
|
if (totalBounds.min.y < 0.0f) {
|
||
|
position.y = Math.Abs(totalBounds.min.y - position.y);
|
||
|
}
|
||
|
else {
|
||
|
position.y = totalBounds.max.y - position.y;
|
||
|
}
|
||
|
|
||
|
root.gameObject.transform.position = position;
|
||
|
}
|
||
|
|
||
|
private static bool SelectionHasAnimation(GameObject selectedObject) {
|
||
|
if (selectedObject == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Animation[] filters = selectedObject.GetComponentsInChildren<Animation>();
|
||
|
|
||
|
if (filters == null || filters.Length == 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* O(2n) operation
|
||
|
*/
|
||
|
public static void CenterMesh(GameObject root) {
|
||
|
MeshFilter[] filters = root.GetComponentsInChildren<MeshFilter>();
|
||
|
|
||
|
Bounds totalBounds = new Bounds();
|
||
|
|
||
|
int count = filters.Length;
|
||
|
|
||
|
// find the center pivot of ALL meshes
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
if (filters[i] != null) {
|
||
|
Mesh mesh = filters[i].sharedMesh;
|
||
|
|
||
|
if (mesh != null) {
|
||
|
Vector3[] positions = mesh.vertices;
|
||
|
int pCount = positions.Length;
|
||
|
|
||
|
for (int j = 0; j < pCount; j++) {
|
||
|
totalBounds.Encapsulate(positions[j]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float pivotX = (totalBounds.min.x + totalBounds.max.x) / 2.0f;
|
||
|
float pivotY = (totalBounds.min.y + totalBounds.max.y) / 2.0f;
|
||
|
float pivotZ = (totalBounds.min.z + totalBounds.max.z) / 2.0f;
|
||
|
|
||
|
Vector3 center = new Vector3(pivotX, pivotY, pivotZ);
|
||
|
|
||
|
// we calculate the min and max, we require this data to center
|
||
|
// everything properly
|
||
|
|
||
|
// now we need to displace all vertices, so loop again
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
if (filters[i] != null) {
|
||
|
Mesh mesh = Mesh.Instantiate(filters[i].sharedMesh);
|
||
|
|
||
|
if (mesh != null) {
|
||
|
Vector3[] positions = mesh.vertices;
|
||
|
int pCount = positions.Length;
|
||
|
|
||
|
for (int j = 0; j < pCount; j++) {
|
||
|
positions[j] = positions[j] - center;
|
||
|
}
|
||
|
|
||
|
mesh.vertices = positions;
|
||
|
}
|
||
|
|
||
|
filters[i].sharedMesh = mesh;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pivotCheck.Add(root.GetInstanceID());
|
||
|
}
|
||
|
}
|
||
|
}
|