diff --git a/Editor/ADTUtility.cs b/Editor/ADTUtility.cs new file mode 100644 index 0000000..75c0b49 --- /dev/null +++ b/Editor/ADTUtility.cs @@ -0,0 +1,147 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; + +namespace WowUnity +{ + public class ADTUtility + { + public static bool IsAdtObj(string path) + { + return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+(_.+)?\.obj$"); + } + + public static bool IsAdtAny(string path) + { + return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+(_.+)?\.(prefab|obj)$"); + } + + public static void PostProcessImports(List paths) + { + var total = 0f; + var itemsProcessed = 0f; + + List<(string, GameObject)> instances = new(); + AssetDatabase.StartAssetEditing(); + try + { + foreach (var path in paths) + { + if (M2Utility.FindPrefab(path) != null) + { + continue; + } + + var imported = AssetDatabase.LoadAssetAtPath(path); + Renderer[] renderers = imported.GetComponentsInChildren(); + total += renderers.Count() + 1; + } + + var mainDataPath = Application.dataPath.Replace("Assets", ""); + foreach (var path in paths) + { + if (M2Utility.FindPrefab(path) != null) + { + continue; + } + + var importedInstance = M2Utility.InstantiateImported(path); + + var dirName = Path.GetDirectoryName(path); + + Renderer[] renderers = importedInstance.GetComponentsInChildren(); + + foreach (var renderer in renderers) + { + var pathToMetadata = $"{dirName}/tex_{renderer.name}.json"; + + if (EditorUtility.DisplayCancelableProgressBar("Creating terrain materials.", pathToMetadata, itemsProcessed / total)) + { + return; + } + + var sr = new StreamReader(mainDataPath + pathToMetadata); + var jsonData = sr.ReadToEnd(); + sr.Close(); + + var metadata = JsonConvert.DeserializeObject(jsonData); + if (metadata.layers.Count == 0) + { + continue; + } + + for (var idx = 0; idx < metadata.layers.Count; idx++) + { + var texture = metadata.layers[idx]; + texture.assetPath = Path.GetRelativePath(mainDataPath, Path.GetFullPath(Path.Join(dirName, texture.file))); + metadata.layers[idx] = texture; + } + + renderer.material = MaterialUtility.GetTerrainMaterial(dirName, renderer.name, metadata); + itemsProcessed++; + } + + instances.Add((path, importedInstance)); + } + } + catch (System.Exception) + { + Debug.LogError($"failed processing terrain materials"); + throw; + } + finally + { + AssetDatabase.StopAssetEditing(); + AssetDatabase.SaveAssets(); + EditorUtility.ClearProgressBar(); + } + + try + { + foreach (var (path, instance) in instances) + { + M2Utility.SaveAsPrefab(instance, path); + itemsProcessed++; + } + } finally + { + EditorUtility.ClearProgressBar(); + } + } + + public class Tex + { + public List layers; + } + + public class Layer + { + public uint index; + public uint effectID; + public float scale; + public uint fileDataID; + public string file; + public string assetPath; + } + + public class EffectModel + { + public int fileDataID; + public string fileName; + } + + public class Effect + { + public int ID; + public int Density; + public int Sound; + public List DoodadID; + public List DoodadWeight; + public Dictionary DoodadModelIDs; + } + } +} diff --git a/Editor/TerrainMaterialGenerator.cs.meta b/Editor/ADTUtility.cs.meta similarity index 83% rename from Editor/TerrainMaterialGenerator.cs.meta rename to Editor/ADTUtility.cs.meta index d2f88b4..cd08256 100644 --- a/Editor/TerrainMaterialGenerator.cs.meta +++ b/Editor/ADTUtility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3e9ae32f6d20954408daeb007feace08 +guid: ed7b6e837080a3c489dd2bb3ea763cb3 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/AssetConversionManager.cs b/Editor/AssetConversionManager.cs index d9fbaf5..bba4227 100644 --- a/Editor/AssetConversionManager.cs +++ b/Editor/AssetConversionManager.cs @@ -1,18 +1,222 @@ -using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; +using System.Linq; using UnityEditor; using UnityEngine; namespace WowUnity { - class AssetConversionManager + public class AssetConversionManager { - public static void ProcessAssets() + private static readonly ConcurrentQueue importedModelPathQueue = new(); + private static readonly ConcurrentQueue importedWMOPathQueue = new(); + private static readonly ConcurrentQueue physicsQueue = new(); + private static bool isBusy = false; + + public static void QueuePostprocess(string filePath) + { + importedModelPathQueue.Enqueue(filePath); + } + + public static bool HasQueue() + { + return importedModelPathQueue.Count + importedWMOPathQueue.Count + physicsQueue.Count > 0; + } + + public static bool IsBusy() + { + return isBusy; + } + + public static string ReadAssetJson(string path) + { + var dirName = Path.GetDirectoryName(path); + string pathToMetadata = dirName + "/" + Path.GetFileNameWithoutExtension(path) + ".json"; + string mainDataPath = Application.dataPath.Replace("Assets", ""); + + var sr = new StreamReader(mainDataPath + pathToMetadata); + var jsonData = sr.ReadToEnd(); + sr.Close(); + + return jsonData; + } + + public static void RunPostProcessImports() + { + var itemsToProcess = importedModelPathQueue.Count; + var itemsProcessed = 0f; + + List<(string, TextAsset)> hasPlacement = new(); + + AssetDatabase.StartAssetEditing(); + try + { + while (importedModelPathQueue.TryDequeue(out string path)) + { + Debug.Log($"{path}: postprocessing"); + + var jsonData = ReadAssetJson(path); + + if (WMOUtility.IsWMO(jsonData)) + { + // process this separately because of StartAssetEditing issues + importedWMOPathQueue.Enqueue(path); + continue; + } + + if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW assets", path, itemsProcessed / itemsToProcess)) + { + return; + } + + M2Utility.PostProcessImport(path, jsonData); + + // process this separately because of StartAssetEditing issues + physicsQueue.Enqueue(path); + + itemsProcessed++; + } + } + finally + { + AssetDatabase.StopAssetEditing(); + AssetDatabase.SaveAssets(); + } + + while (importedWMOPathQueue.TryDequeue(out string path)) + { + Debug.Log($"{path}: postprocessing"); + + if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW WMOs", path, itemsProcessed / itemsToProcess)) + { + return; + } + + WMOUtility.PostProcessImport(path, ReadAssetJson(path)); + SetupPhysics(path); + + TextAsset placementData = AssetDatabase.LoadAssetAtPath(path.Replace(".obj", "_ModelPlacementInformation.csv")); + if (placementData != null) + { + hasPlacement.Add((path, placementData)); + } + + itemsProcessed++; + } + + itemsToProcess = physicsQueue.Count; + itemsProcessed = 0f; + + while (physicsQueue.TryDequeue(out string path)) + { + Debug.Log($"{path}: setup physics"); + + if (EditorUtility.DisplayCancelableProgressBar("Setting up physics", path, itemsProcessed / itemsToProcess)) + { + return; + } + + SetupPhysics(path); + + itemsProcessed++; + } + + itemsToProcess = hasPlacement.Count; + itemsProcessed = 0f; + + foreach (var (path, placementData) in hasPlacement) + { + if (EditorUtility.DisplayCancelableProgressBar("Placing doodads", path, itemsProcessed / itemsToProcess)) + { + return; + } + Debug.Log($"{path}: placing models"); + ItemCollectionUtility.PlaceModels(M2Utility.FindPrefab(path), placementData); + itemsProcessed++; + } + } + + public static void SetupPhysics(string path) + { + GameObject physicsPrefab = AssetDatabase.LoadAssetAtPath(path.Replace(".obj", ".phys.obj")); + if (physicsPrefab == null) + { + return; + } + + var prefab = M2Utility.FindPrefab(path); + + if (prefab.transform.Find("Collision") != null) + { + return; + } + + var collisionMesh = physicsPrefab.GetComponentInChildren(); + if (collisionMesh == null) + { + return; + } + + var prefabInst = PrefabUtility.InstantiatePrefab(prefab) as GameObject; + prefabInst.GetComponentsInChildren().ToList().ForEach(collider => Object.DestroyImmediate(collider)); + + GameObject collider = new(); + collider.transform.SetParent(prefabInst.transform); + collider.name = "Collision"; + MeshCollider parentCollider = collider.AddComponent(); + parentCollider.sharedMesh = collisionMesh.sharedMesh; + PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); + PrefabUtility.SavePrefabAsset(prefab); + + Object.DestroyImmediate(prefabInst); + } + + public static void PostProcessImports() + { + if (importedModelPathQueue.Count + importedWMOPathQueue.Count + physicsQueue.Count == 0) + { + return; + } + + isBusy = true; + + try + { + RunPostProcessImports(); + } + finally + { + EditorUtility.ClearProgressBar(); + } + + Debug.Log("PostProcessImports done"); + isBusy = false; + } + + public static void JobPostprocessAllAssets() { - EditorApplication.update -= ProcessAssets; + importedModelPathQueue.Clear(); + importedWMOPathQueue.Clear(); + physicsQueue.Clear(); - M2Utility.PostProcessImports(); - ItemCollectionUtility.BeginQueue(); + EditorUtility.DisplayProgressBar("Postprocessing WoW assets", "Looking for assets.", 0); + try + { + string[] allAssets = AssetDatabase.GetAllAssetPaths(); + foreach (string path in allAssets) + { + if (WoWExportUnityPostprocessor.ValidAsset(path) && !ADTUtility.IsAdtObj(path)) + { + QueuePostprocess(path); + } + } + } + finally + { + EditorUtility.ClearProgressBar(); + } + PostProcessImports(); } } } diff --git a/Editor/ItemCollectionUtility.cs b/Editor/ItemCollectionUtility.cs index fb75e48..f717349 100644 --- a/Editor/ItemCollectionUtility.cs +++ b/Editor/ItemCollectionUtility.cs @@ -1,5 +1,3 @@ -using System.Collections; -using System.Collections.Generic; using System.Globalization; using UnityEngine; using UnityEditor; @@ -18,15 +16,7 @@ public class ItemCollectionUtility public static readonly float MAP_SIZE = MAXIMUM_DISTANCE_FROM_ORIGIN * 2f; public static readonly float ADT_SIZE = MAP_SIZE / 64f; - private static List queuedPlacementInformationPaths = new List(); - private static List missingFilesInQueue = new List(); - - public static bool isADT(TextAsset modelPlacementInformation) - { - return Regex.IsMatch(modelPlacementInformation.name, @"adt_\d{2}_\d{2}"); - } - - public static void GenerateADT(GameObject prefab, TextAsset modelPlacementInformation) + public static void PlaceModels(GameObject prefab, TextAsset modelPlacementInformation) { GameObject instantiatedGameObject = (GameObject)PrefabUtility.InstantiatePrefab(prefab); instantiatedGameObject.isStatic = true; @@ -35,32 +25,62 @@ public static void GenerateADT(GameObject prefab, TextAsset modelPlacementInform childTransform.gameObject.isStatic = true; } - string path = AssetDatabase.GetAssetPath(prefab); + PlaceModelsOnPrefab(instantiatedGameObject, modelPlacementInformation); - ParseFileAndSpawnDoodads(instantiatedGameObject, modelPlacementInformation); + PrefabUtility.ApplyPrefabInstance(instantiatedGameObject, InteractionMode.AutomatedAction); + PrefabUtility.SavePrefabAsset(prefab); - string parentPath = AssetDatabase.GetAssetPath(prefab); + Object.DestroyImmediate(instantiatedGameObject); + } - if (Path.GetExtension(parentPath) == ".prefab") + private static Transform GetOrCreateRoot(Transform destination, string name) + { + var root = destination.Find(name); + if (root == null) { - PrefabUtility.ApplyPrefabInstance(instantiatedGameObject, InteractionMode.AutomatedAction); - PrefabUtility.SavePrefabAsset(prefab); + var newRootGO = new GameObject(name) { isStatic = true }; + newRootGO.transform.parent = destination; + root = newRootGO.transform; } - else + return root; + } + + private static void PlaceModelsOnPrefab(GameObject instantiatedPrefabGObj, TextAsset modelPlacementInformation) + { + var isAdt = Regex.IsMatch(modelPlacementInformation.name, @"adt_\d+_\d+"); + + var doodadSetRoot = GetOrCreateRoot(instantiatedPrefabGObj.transform, isAdt ? "EnvironmentSet" : "DoodadSets"); + + if (doodadSetRoot == null) { - PrefabUtility.SaveAsPrefabAsset(instantiatedGameObject, parentPath.Replace(Path.GetExtension(parentPath), ".prefab")); + Debug.LogWarning("No doodad set root found in " + instantiatedPrefabGObj.name); + return; } - Object.DestroyImmediate(instantiatedGameObject); + if (doodadSetRoot.Find("doodadsplaced") != null) + return; + + ParseFileAndSpawnDoodads(doodadSetRoot, modelPlacementInformation); + if (isAdt) + ParseFileAndSpawnDoodads(GetOrCreateRoot(instantiatedPrefabGObj.transform, "WMOSet"), modelPlacementInformation, typeToPlace: "wmo"); + + var placed = new GameObject("doodadsplaced"); + placed.transform.parent = doodadSetRoot.transform; } - private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, TextAsset modelPlacementInformation) + private static void ParseFileAndSpawnDoodads(Transform doodadSetRoot, TextAsset modelPlacementInformation, string typeToPlace = "m2", bool useSetSubtrees = true) { + var isAdt = Regex.IsMatch(modelPlacementInformation.name, @"adt_\d+_\d+"); + + var placementFileDir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(modelPlacementInformation)); string[] records = modelPlacementInformation.text.Split(CSV_LINE_SEPERATOR); foreach (string record in records.Skip(1)) { string[] fields = record.Split(CSV_COLUMN_SEPERATOR); - string doodadPath = Path.GetDirectoryName(PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(instantiatedPrefabGObj)) + Path.DirectorySeparatorChar + fields[0]; + if (fields.Length < 11) + continue; + + string doodadPath = placementFileDir + Path.DirectorySeparatorChar + fields[0]; doodadPath = Path.GetFullPath(doodadPath); doodadPath = $"Assets{Path.DirectorySeparatorChar}" + doodadPath.Substring(Application.dataPath.Length + 1); //This is so nifty :3 @@ -68,8 +88,11 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, Quaternion doodadRotation = Quaternion.identity; float doodadScale = float.Parse(fields[8], CultureInfo.InvariantCulture); - if (isADT(modelPlacementInformation)) + if (isAdt) { + var doodadType = fields[10]; + if (doodadType != typeToPlace) + continue; doodadPosition.x = MAXIMUM_DISTANCE_FROM_ORIGIN - float.Parse(fields[1], CultureInfo.InvariantCulture); doodadPosition.z = (MAXIMUM_DISTANCE_FROM_ORIGIN - float.Parse(fields[3], CultureInfo.InvariantCulture)) * -1f; @@ -81,34 +104,56 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, eulerRotation.z = float.Parse(fields[4], CultureInfo.InvariantCulture) * -1; doodadRotation.eulerAngles = eulerRotation; + + var spawned = SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, doodadSetRoot); + if (doodadType == "wmo") + { + var doodadSets = fields[13].Split(","); + foreach (var setName in doodadSets) + { + var childObj = spawned.transform.Find($"DoodadSets/{setName}"); + if (childObj != null) + { + childObj.gameObject.SetActive(true); + } + } + } } else { doodadPosition = new Vector3( - float.Parse(fields[1], CultureInfo.InvariantCulture), - float.Parse(fields[3], CultureInfo.InvariantCulture), + float.Parse(fields[1], CultureInfo.InvariantCulture), + float.Parse(fields[3], CultureInfo.InvariantCulture), float.Parse(fields[2], CultureInfo.InvariantCulture) ); doodadRotation = new Quaternion( - float.Parse(fields[5], CultureInfo.InvariantCulture) * -1, - float.Parse(fields[7], CultureInfo.InvariantCulture), - float.Parse(fields[6], CultureInfo.InvariantCulture) * -1, + float.Parse(fields[5], CultureInfo.InvariantCulture) * -1, + float.Parse(fields[7], CultureInfo.InvariantCulture), + float.Parse(fields[6], CultureInfo.InvariantCulture) * -1, float.Parse(fields[4], CultureInfo.InvariantCulture) * -1 ); - } - SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, instantiatedPrefabGObj.transform); + var setName = fields[9]; + var placementRoot = doodadSetRoot; + if (useSetSubtrees) + { + placementRoot = GetOrCreateRoot(doodadSetRoot, setName); + placementRoot.gameObject.SetActive(setName == "Set_$DefaultGlobal"); + } + + SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, placementRoot); + } } } - private static void SpawnDoodad(string path, Vector3 position, Quaternion rotation, float scaleFactor, Transform parent) + private static GameObject SpawnDoodad(string path, Vector3 position, Quaternion rotation, float scaleFactor, Transform parent) { - GameObject exisitingPrefab = M2Utility.FindOrCreatePrefab(path); + GameObject exisitingPrefab = M2Utility.FindPrefab(path); if (exisitingPrefab == null) { Debug.LogWarning("Object was not spawned because it could not be found: " + path); - return; + return null; } GameObject newDoodadInstance = PrefabUtility.InstantiatePrefab(exisitingPrefab, parent) as GameObject; @@ -116,36 +161,8 @@ private static void SpawnDoodad(string path, Vector3 position, Quaternion rotati newDoodadInstance.transform.localPosition = position; newDoodadInstance.transform.localRotation = rotation; newDoodadInstance.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor); - } - - public static void QueuePlacementData(string filePath) - { - queuedPlacementInformationPaths.Add(filePath); - } - - public static void BeginQueue() - { - if (queuedPlacementInformationPaths.Count == 0) - { - return; - } - - List iteratingList = new List(queuedPlacementInformationPaths); - - foreach (string path in iteratingList) - { - TextAsset placementData = AssetDatabase.LoadAssetAtPath(path); - string prefabPath = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + Path.GetFileName(path).Replace("_ModelPlacementInformation.csv", ".obj"); - GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabPath); - GenerateADT(prefab, placementData); - - queuedPlacementInformationPaths.Remove(path); - } - foreach (string missingFilePath in missingFilesInQueue) - { - Debug.Log("Warning, import could not be found: " + missingFilePath); - } + return newDoodadInstance; } } } diff --git a/Editor/M2Utility.cs b/Editor/M2Utility.cs index 7e542aa..21852eb 100644 --- a/Editor/M2Utility.cs +++ b/Editor/M2Utility.cs @@ -2,72 +2,59 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEditor; using UnityEngine; namespace WowUnity { - class M2Utility + public class M2Utility { - private static List importedModelPathQueue = new List(); - - public static void QueueMetadata(string filePath) + public static void PostProcessImport(string path, string jsonData) { - importedModelPathQueue.Add(filePath); - } + var metadata = JsonConvert.DeserializeObject(jsonData); + if (metadata.fileType != "m2") { + return; + } - public static void PostProcessImports() - { - if (importedModelPathQueue.Count == 0) + Debug.Log($"{path}: processing m2"); + + if (FindPrefab(path) != null) { return; } - List iteratingList = new List(importedModelPathQueue); + ProcessTextures(metadata.textures, Path.GetDirectoryName(path)); - foreach (string path in iteratingList) - { - M2 metadata = ReadMetadataFor(path); + var importedInstance = InstantiateImported(path); - if (metadata == null) - continue; + Renderer[] renderers = importedInstance.GetComponentsInChildren(); - GameObject prefab = FindOrCreatePrefab(path); + var skinMaterials = MaterialUtility.GetSkinMaterials(metadata); - if (metadata.textureTransforms.Count > 0 && metadata.textureTransforms[0].translation.timestamps.Count > 0) - { - for (int i = 0; i < metadata.textureTransforms.Count; i++) - { - AnimationClip newClip = AnimationUtility.CreateAnimationClip(metadata.textureTransforms[i]); - AssetDatabase.CreateAsset(newClip, Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + "[" + i + "]" + ".anim"); - } - } + // Configure materials + for (uint rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++) + { + var renderer = renderers[rendererIndex]; + var (material, isMatDoubleSided) = skinMaterials[rendererIndex]; + renderer.material = material; } + AssetDatabase.Refresh(); - //Processing done: remove all paths from the queue - importedModelPathQueue.Clear(); - } - - public static GameObject FindOrCreatePrefab(string path) - { - string prefabPath = Path.ChangeExtension(path, "prefab"); - GameObject existingPrefab = AssetDatabase.LoadAssetAtPath(prefabPath); + SaveAsPrefab(importedInstance, path); - if (existingPrefab == null) + if (metadata.textureTransforms.Count > 0 && metadata.textureTransforms[0].translation.timestamps.Count > 0) { - return GeneratePrefab(path); + for (int i = 0; i < metadata.textureTransforms.Count; i++) + { + AnimationClip newClip = AnimationUtility.CreateAnimationClip(metadata.textureTransforms[i]); + AssetDatabase.CreateAsset(newClip, Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + "[" + i + "]" + ".anim"); + } } - - return existingPrefab; } - public static GameObject GeneratePrefab(string path) + public static GameObject InstantiateImported(string path) { - string prefabPath = Path.ChangeExtension(path, "prefab"); - GameObject importedModelObject = AssetDatabase.LoadAssetAtPath(path); + var importedModelObject = AssetDatabase.LoadAssetAtPath(path); if (importedModelObject == null) { @@ -75,12 +62,8 @@ public static GameObject GeneratePrefab(string path) return null; } - ConfigureRendererMaterials(importedModelObject); - - ModelImporter modelImporter = ModelImporter.GetAtPath(path) as ModelImporter; - modelImporter.SearchAndRemapMaterials(ModelImporterMaterialName.BasedOnMaterialName, ModelImporterMaterialSearch.RecursiveUp); - - GameObject rootModelInstance = PrefabUtility.InstantiatePrefab(importedModelObject) as GameObject; + var rootObj = new GameObject() { isStatic = true }; + var rootModelInstance = PrefabUtility.InstantiatePrefab(importedModelObject, rootObj.transform) as GameObject; //Set the object as static, and all it's child objects rootModelInstance.isStatic = true; @@ -89,77 +72,35 @@ public static GameObject GeneratePrefab(string path) childTransform.gameObject.isStatic = true; } - GameObject newPrefab = PrefabUtility.SaveAsPrefabAssetAndConnect(rootModelInstance, prefabPath, InteractionMode.AutomatedAction); - AssetDatabase.Refresh(); - UnityEngine.Object.DestroyImmediate(rootModelInstance); - - return newPrefab; + return rootObj; } - private static void ConfigureRendererMaterials(GameObject importedModelObject) + public static void SaveAsPrefab(GameObject gameObject, string path) { - //Manage materials for imported models. - - //First, we need to sample all renderers that belong to the specified game object. - Renderer[] renderers = importedModelObject.GetComponentsInChildren(); - - //Now we will loop through all renderers present in the game object - //and call the MaterialUtility to create the asset. - for (int rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++) - { - for (int materialIndex = 0; materialIndex < renderers[rendererIndex].sharedMaterials.Length; materialIndex++) - { - //We don't need to worry about repeat materials here, - //because the CreateMaterialAsset already handles this case for us. - MaterialUtility.ExtractMaterialFromAsset(renderers[rendererIndex].sharedMaterials[materialIndex]); - } - } - AssetDatabase.Refresh(); + PrefabUtility.SaveAsPrefabAsset(gameObject, Path.ChangeExtension(path, ".prefab")); + UnityEngine.Object.DestroyImmediate(gameObject); } - public static M2 ReadMetadataFor(string path) + public static GameObject FindPrefab(string path) { - string pathToMetadata = Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + ".json"; - - if (!File.Exists(pathToMetadata)) - { - return null; - } - - var sr = new StreamReader(Application.dataPath.Replace("Assets", "") + pathToMetadata); - var fileContents = sr.ReadToEnd(); - sr.Close(); - - return JsonConvert.DeserializeObject(fileContents); + string prefabPath = Path.ChangeExtension(path, "prefab"); + return AssetDatabase.LoadAssetAtPath(prefabPath); } - public static Material GetMaterialData(string materialName, M2 metadata) - { - Material data = new Material(); - data.flags = 0; - data.blendingMode = 0; + public static void ProcessTextures(List textures, string dirName) { + string mainDataPath = Application.dataPath.Replace("Assets", ""); - //I have no idea why they sometimes don't match sizes. - // I'm guessing if there's no material entry, default is intended. - for(int i = 0; i < metadata.textures.Count; i++) - { - Texture texture = metadata.textures[i]; - if (texture.mtlName != materialName) - continue; - - if (metadata.materials.Count <= i) - i = metadata.materials.Count - 1; - - data = metadata.materials[i]; - break; + for (var idx = 0; idx < textures.Count; idx++) { + var texture = textures[idx]; + texture.assetPath = Path.GetRelativePath(mainDataPath, Path.GetFullPath(Path.Join(dirName, texture.fileNameExternal))); + textures[idx] = texture; } - - return data; } [Serializable] public class M2 { + public string fileType; public uint fileDataID; public string fileName; public string internalName; @@ -189,9 +130,11 @@ public struct SubMesh [Serializable] public struct TextureUnit { - public uint skinSelectionIndex; + public uint skinSectionIndex; public uint geosetIndex; + public uint materialIndex; public uint colorIndex; + public uint textureComboIndex; } [Serializable] @@ -199,7 +142,9 @@ public struct Texture { public string fileNameInternal; public string fileNameExternal; + public string assetPath; public string mtlName; + public string uniqMtlName; public short flag; public uint fileDataID; } diff --git a/Editor/MaterialUtility.cs b/Editor/MaterialUtility.cs index e47f2e0..9b8f9c3 100644 --- a/Editor/MaterialUtility.cs +++ b/Editor/MaterialUtility.cs @@ -1,19 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using UnityEditor; -using UnityEditor.AssetImporters; using UnityEngine; namespace WowUnity { - class MaterialUtility + public class MaterialUtility { - public const string LIT_SHADER = "Universal Render Pipeline/Simple Lit"; + public const string LIT_SHADER = "Universal Render Pipeline/Lit"; public const string UNLIT_SHADER = "Universal Render Pipeline/Unlit"; public const string EFFECT_SHADER = "Universal Render Pipeline/Particles/Unlit"; public const string ADT_CHUNK_SHADER = "wow.unity/TerrainChunk"; @@ -38,55 +33,57 @@ public enum BlendModes : short BlendAdd = 7 } - public static Material ConfigureMaterial(MaterialDescription description, Material material, string modelImportPath, M2Utility.M2 metadata) + public enum MaterialFor: short { - if (Regex.IsMatch(Path.GetFileNameWithoutExtension(modelImportPath), @"adt_\d{2}_\d{2}")) - return ProcessADTMaterial(description, material, modelImportPath); + M2 = 0, + WMO = 1, + } - M2Utility.Material materialData = M2Utility.GetMaterialData(material.name, metadata); - Color materialColor = Color.white; - if (metadata != null && metadata.colors.Count > 0) - { - materialColor = ProcessMaterialColors(material, metadata); + public static Material GetMaterial(M2Utility.Texture texture, MaterialFor materialFor, short flags, uint blendingMode, int shader, Color materialColor) { + var colorName = materialColor == Color.white ? "W" : ColorUtility.ToHtmlStringRGBA(materialColor); + var matName = $"{Path.GetFileNameWithoutExtension(texture.fileNameExternal)}_TF{texture.flag}_F{flags:X}_B{blendingMode:X}_S{shader:X}_C{colorName}"; + var assetMatPath = Path.Join(Path.GetDirectoryName(texture.assetPath), $"{matName}.mat"); + + var material = AssetDatabase.LoadAssetAtPath(assetMatPath); + if (material != null) { + return material; } - - material.shader = Shader.Find(LIT_SHADER); - material.SetColor("_BaseColor", materialColor); - // Read a texture property from the material description. - TexturePropertyDescription textureProperty; - if (description.TryGetProperty("DiffuseColor", out textureProperty) && textureProperty.texture != null) - { - // Assign the texture to the material. - material.SetTexture("_MainTex", textureProperty.texture); + Debug.Log($"{matName}: material does not exist, creating."); + + material = new Material(Shader.Find(LIT_SHADER)); + material.SetFloat("_WorkflowMode", 0); + material.SetFloat("_Smoothness", 0); + if (shader == 1) { + material.SetFloat("_Smoothness", 1); + material.SetFloat("_SmoothnessTextureChannel", 1); } - - ProcessFlagsForMaterial(material, materialData); - return material; - } - public static void ProcessFlagsForMaterial(Material material, M2Utility.Material data) - { //Flags first - if ((data.flags & (short)MaterialFlags.Unlit) != (short)MaterialFlags.None) + if ((flags & (short)MaterialFlags.Unlit) != (short)MaterialFlags.None) { material.shader = Shader.Find(UNLIT_SHADER); + material.SetFloat("_Cull", 0); } - if ((data.flags & (short)MaterialFlags.TwoSided) != (short)MaterialFlags.None) + Texture assetTexture = AssetDatabase.LoadAssetAtPath(texture.assetPath); + material.SetTexture("_BaseMap", assetTexture); + material.SetColor("_BaseColor", materialColor); + + if ((flags & (short)MaterialFlags.TwoSided) != (short)MaterialFlags.None) { material.doubleSidedGI = true; material.SetFloat("_Cull", 0); } //Now blend modes - if (data.blendingMode == (short)BlendModes.AlphaKey) + if (blendingMode == (short)BlendModes.AlphaKey) { material.EnableKeyword("_ALPHATEST_ON"); material.SetFloat("_AlphaClip", 1); } - if (data.blendingMode == (short)BlendModes.Alpha) + if (blendingMode == (short)BlendModes.Alpha) { material.SetOverrideTag("RenderType", "Transparent"); material.SetFloat("_Blend", 0); @@ -94,125 +91,116 @@ public static void ProcessFlagsForMaterial(Material material, M2Utility.Material material.SetFloat("_ZWrite", 0); } - if (data.blendingMode == (short)BlendModes.Add) + if (blendingMode == (short)BlendModes.Add) { material.SetOverrideTag("RenderType", "Transparent"); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent; material.SetFloat("_Cutoff", 0); - material.SetFloat("_Blend", 1); + material.SetFloat("_Blend", 2); material.SetFloat("_Surface", 1); material.SetFloat("_SrcBlend", 1); material.SetFloat("_DstBlend", 1); material.SetFloat("_ZWrite", 0); material.SetShaderPassEnabled("ShadowCaster", false); } - } - - public static Color ProcessMaterialColors(Material material, M2Utility.M2 metadata) - { - int i, j, k; - Color newColor = Color.white; - if (metadata.skin == null || metadata.skin.textureUnits.Count <= 0) - { - return newColor; - } - for (i = 0; i < metadata.textures.Count; i++) + if (materialFor == MaterialFor.WMO && (flags & 16) == 16) { - if (material.name == metadata.textures[i].mtlName) - break; + material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive; + material.SetColor("_EmissionColor", Color.white); + material.SetTexture("_EmissionMap", assetTexture); } - for (j = 0; j < metadata.skin.textureUnits.Count; j++) - { - if (metadata.skin.textureUnits[j].geosetIndex == i) - break; - } + AssetDatabase.CreateAsset(material, assetMatPath); - if (j < metadata.skin.textureUnits.Count) - k = (int)metadata.skin.textureUnits[j].colorIndex; - else - return newColor; - - if (k < metadata.colors.Count) - { - newColor.r = metadata.colors[k].color.values[0][0][0]; - newColor.g = metadata.colors[k].color.values[0][0][1]; - newColor.b = metadata.colors[k].color.values[0][0][2]; - newColor.a = 1; - } - - return newColor; + return material; } - public static Material ProcessADTMaterial(MaterialDescription description, Material material, string modelImportPath) + public static Dictionary GetSkinMaterials(M2Utility.M2 metadata) { - material.shader = Shader.Find(ADT_CHUNK_SHADER); + Dictionary mats = new(); - TexturePropertyDescription textureProperty; - if (description.TryGetProperty("DiffuseColor", out textureProperty) && textureProperty.texture != null) - { - material.SetTexture("_BaseMap", textureProperty.texture); - } + foreach (var textureUnit in metadata.skin.textureUnits) { + var texture = metadata.textures[metadata.textureCombos[checked((int)textureUnit.textureComboIndex)]]; + var unitMat = metadata.materials[checked((int)textureUnit.materialIndex)]; - LoadMetadataAndConfigureADT(material, modelImportPath); + var materialColor = Color.white; + Debug.Log($"color: {textureUnit.colorIndex}, {textureUnit.colorIndex != 0xffff}"); + if (textureUnit.colorIndex < 0xffff) + { + var colorValue = metadata.colors[(int)textureUnit.colorIndex].color.values[0][0]; + materialColor = new Color() { r = colorValue[0], g = colorValue[1], b = colorValue[2], a = 1 }; + } - return material; + mats[textureUnit.skinSectionIndex] = ( + GetMaterial(texture, MaterialFor.M2, unitMat.flags, unitMat.blendingMode, 0, materialColor), + (unitMat.flags & (short)MaterialFlags.TwoSided) != (short)MaterialFlags.None); // is two-sided + } + + AssetDatabase.SaveAssets(); + return mats; } - public static void LoadMetadataAndConfigureADT(Material mat, string assetPath) + public static Dictionary GetWMOMaterials(WMOUtility.WMO metadata) { - string jsonFilePath = Path.GetDirectoryName(assetPath) + Path.DirectorySeparatorChar + mat.name + ".json"; - var sr = new StreamReader(Application.dataPath.Replace("Assets", "") + jsonFilePath); - var fileContents = sr.ReadToEnd(); - sr.Close(); + Dictionary mats = new(); - TerrainMaterialGenerator.Chunk newChunk = JsonUtility.FromJson(fileContents); + var texturesById = metadata.textures.ToDictionary((item) => item.fileDataID); - Vector4 scaleVector = new Vector4(); - TerrainMaterialGenerator.Layer currentLayer; - for (int i = 0; i < newChunk.layers.Count; i++) - { - currentLayer = newChunk.layers[i]; - string texturePath = Path.Combine(Path.GetDirectoryName(@assetPath), @currentLayer.file); - texturePath = Path.GetFullPath(texturePath); - texturePath = texturePath.Substring(texturePath.IndexOf($"Assets{Path.DirectorySeparatorChar}")); + foreach (var group in metadata.groups) { + for (var batchIdx = 0; batchIdx < group.renderBatches.Count; batchIdx++) { + var batch = group.renderBatches[batchIdx]; + var batchMat = metadata.materials[checked((int)batch.materialID)]; + var texture = texturesById[batchMat.texture1]; - Texture2D layerTexture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D)); - mat.SetTexture("Layer_" + i, layerTexture); - scaleVector[i] = currentLayer.scale; + var material = GetMaterial(texture, MaterialFor.WMO, batchMat.flags, batchMat.blendMode, batchMat.shader, Color.white); + mats[$"{group.groupName}{batchIdx}"] = material; + } } - mat.SetVector("Scale", scaleVector); + AssetDatabase.SaveAssets(); + return mats; } - public static void ExtractMaterialFromAsset(Material material) + public static Material GetTerrainMaterial(string dirName, string chunkName, ADTUtility.Tex metadata) { - string assetPath = AssetDatabase.GetAssetPath(material); - string newMaterialPath = "Assets/Materials/" + material.name + ".mat"; - Material newMaterialAsset; - - if (!Directory.Exists("Assets/Materials")) + var matDir = Path.Join(dirName, "terrain_materials"); + if (!Directory.Exists(matDir)) { - Directory.CreateDirectory("Assets/Materials"); + Directory.CreateDirectory(matDir); } - - if (!File.Exists(newMaterialPath)) + var assetMatPath = Path.Join(matDir, $"tex_{chunkName}.mat"); + + var assetMat = AssetDatabase.LoadAssetAtPath(assetMatPath); + if (assetMat != null) { - newMaterialAsset = new Material(material); - AssetDatabase.CreateAsset(newMaterialAsset, newMaterialPath); + return assetMat; } - else + + Debug.Log($"{assetMatPath}: material does not exist, creating."); + + var textures = metadata.layers.Select((item) => AssetDatabase.LoadAssetAtPath(item.assetPath)).ToList(); + var mask = AssetDatabase.LoadAssetAtPath(Path.Join(dirName, $"tex_{chunkName}.png")); + + assetMat = new Material(Shader.Find(ADT_CHUNK_SHADER)); + assetMat.SetTexture("_BaseMap", mask); + + Vector4 scaleVector = new Vector4(); + ADTUtility.Layer currentLayer; + for (int i = 0; i < metadata.layers.Count; i++) { - newMaterialAsset = AssetDatabase.LoadAssetAtPath(newMaterialPath); + currentLayer = metadata.layers[i]; + var layerTexture = AssetDatabase.LoadAssetAtPath(currentLayer.assetPath); + assetMat.SetTexture("Layer_" + i, layerTexture); + scaleVector[i] = currentLayer.scale; } - AssetImporter importer = AssetImporter.GetAtPath(assetPath); - importer.AddRemap(new AssetImporter.SourceAssetIdentifier(material), newMaterialAsset); + assetMat.SetVector("Scale", scaleVector); + + AssetDatabase.CreateAsset(assetMat, assetMatPath); - AssetDatabase.WriteImportSettingsIfDirty(assetPath); - AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + return assetMat; } } } diff --git a/Editor/TerrainMaterialGenerator.cs b/Editor/TerrainMaterialGenerator.cs deleted file mode 100644 index e9fde3d..0000000 --- a/Editor/TerrainMaterialGenerator.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -using System.IO; -using System.Linq; -using System; -using System.Text.RegularExpressions; - -public class TerrainMaterialGenerator : EditorWindow -{ - public Shader terrainShader; - public List jsonFiles; - - [MenuItem("Window/Generate Terrain Material Data")] - public static void ShowWindow() - { - EditorWindow.GetWindow(typeof(TerrainMaterialGenerator)); - } - - void OnGUI() - { - GUILayout.Label("Generate Terrain Material Data", EditorStyles.boldLabel); - - // "target" can be any class derrived from ScriptableObject - // (could be EditorWindow, MonoBehaviour, etc) - ScriptableObject target = this; - SerializedObject so = new SerializedObject(target); - SerializedProperty terrainShaderProp = so.FindProperty("terrainShader"); - SerializedProperty jsonFileProp = so.FindProperty("jsonFiles"); - - EditorGUILayout.PropertyField(terrainShaderProp, true); // True means show children - EditorGUILayout.PropertyField(jsonFileProp, true); // True means show children - so.ApplyModifiedProperties(); // Remember to apply modified properties - - GUILayout.Space(5f); - - if (GUILayout.Button("Generate Data")) - { - ParseFile(); - } - } - - void ParseFile() - { - foreach (TextAsset chunkData in jsonFiles) - { - Chunk newChunk = JsonUtility.FromJson(chunkData.text); - - string filePath = AssetDatabase.GetAssetPath(chunkData); - Match match = Regex.Match(filePath, @"tex_\d{2}_\d{2}_\d{1,3}(?=\.json)"); - string materialName = match.Groups[0].Value; - - Material currentChunkMaterial = (Material)AssetDatabase.LoadAssetAtPath("Assets/Materials/" + materialName + ".mat", typeof(Material)); - currentChunkMaterial.shader = terrainShader; - - Vector4 scaleVector = new Vector4(); - Layer currentLayer; - for (int i = 0; i < newChunk.layers.Count; i++) - { - currentLayer = newChunk.layers[i]; - string texturePath = currentLayer.file.Replace("..\\..\\", "Assets\\world geometry\\"); - Debug.Log(texturePath); - Texture2D layerTexture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D)); - currentChunkMaterial.SetTexture("Layer_" + i, layerTexture); - scaleVector[i] = currentLayer.scale; - } - - currentChunkMaterial.SetVector("Scale", scaleVector); - } - } - - [Serializable] - public struct Chunk - { - public List layers; - } - - [Serializable] - public struct Layer - { - public int index; - public int fileDataID; - public int scale; - public string file; - } -} diff --git a/Editor/WMOUtility.cs b/Editor/WMOUtility.cs index 707ca0a..cf144c3 100644 --- a/Editor/WMOUtility.cs +++ b/Editor/WMOUtility.cs @@ -1,12 +1,51 @@ using Newtonsoft.Json; -using System.Collections; using System.Collections.Generic; +using System.IO; +using UnityEditor; using UnityEngine; namespace WowUnity { - class WMOUtility + public class WMOUtility { + public static bool IsWMO(string jsonData) + { + var metadata = JsonConvert.DeserializeObject(jsonData); + return metadata.fileType == "wmo"; + } + + public static void PostProcessImport(string path, string jsonData) + { + var metadata = JsonConvert.DeserializeObject(jsonData); + if (metadata.fileType != "wmo") { + return; + } + + Debug.Log($"{path}: processing wmo"); + + if (M2Utility.FindPrefab(path) != null) + { + return; + } + + M2Utility.ProcessTextures(metadata.textures, Path.GetDirectoryName(path)); + + var importedInstance = M2Utility.InstantiateImported(path); + + Renderer[] renderers = importedInstance.GetComponentsInChildren(); + + var materials = MaterialUtility.GetWMOMaterials(metadata); + + for (uint rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++) + { + var renderer = renderers[rendererIndex]; + renderer.material = materials[renderer.name]; + } + AssetDatabase.Refresh(); + + M2Utility.SaveAsPrefab(importedInstance, path); + } + public static bool AssignVertexColors(WMOUtility.Group group, List gameObjects) { if (gameObjects.Count != group.renderBatches.Count) @@ -54,15 +93,18 @@ static Color[] GetVertexColorsInRange(WMOUtility.Group group, int start, int end public class WMO { + public string fileType; public uint fileDataID; public string fileName; public uint version; - public byte[] ambientColor; + public uint ambientColor; public uint areaTableID; - public BitArray flags; + public short flags; public List groups; public List groupNames; public List textures; + public List materials; + public List doodadSets; } public class Group @@ -79,8 +121,29 @@ public class RenderBatch { public ushort firstVertex; public ushort lastVertex; - public BitArray flags; + public short flags; public uint materialID; } + + public class Material { + public short flags; + public int shader; + public uint blendMode; + public uint texture1; + public uint color1; + public uint color1b; + public uint texture2; + public uint color2; + public uint groupType; + public uint texture3; + public uint color3; + public uint flags3; + } + + public class DoodadSet { + public string name; + public uint firstInstanceIndex; + public uint doodadCount; + } } } diff --git a/Editor/WoWExportUnityPostProcessor.cs b/Editor/WoWExportUnityPostProcessor.cs index 9313143..790e310 100644 --- a/Editor/WoWExportUnityPostProcessor.cs +++ b/Editor/WoWExportUnityPostProcessor.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; using UnityEditor; using UnityEngine; -using UnityEditor.AssetImporters; using System.IO; using System.Text.RegularExpressions; -using System.Linq; -using System; using WowUnity; public class WoWExportUnityPostprocessor : AssetPostprocessor @@ -20,14 +16,17 @@ public override uint GetVersion() return 1; } - static private bool ValidAsset(string path) + static public bool ValidAsset(string path) { - if (!path.Contains(".obj")) + if (!path.EndsWith(".obj")) return false; - if (path.Contains(".phys.obj")) + if (path.EndsWith(".phys.obj")) return false; - return true; + return ( + File.Exists(Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + ".json") || + ADTUtility.IsAdtObj(path) + ); } public void OnPreprocessTexture() @@ -50,32 +49,26 @@ public void OnPreprocessTexture() public void OnPreprocessModel() { + ModelImporter modelImporter = assetImporter as ModelImporter; + if (!ValidAsset(assetPath)) { + if (assetPath.EndsWith(".phys.obj")) + { + modelImporter.bakeAxisConversion = true; + } return; } - ModelImporter modelImporter = assetImporter as ModelImporter; + Debug.Log($"{assetPath}: processing wow model"); + modelImporter.bakeAxisConversion = true; modelImporter.generateSecondaryUV = true; modelImporter.secondaryUVMarginMethod = ModelImporterSecondaryUVMarginMethod.Calculate; modelImporter.secondaryUVMinLightmapResolution = 16; modelImporter.secondaryUVMinObjectScale = 1; - modelImporter.materialImportMode = ModelImporterMaterialImportMode.ImportViaMaterialDescription; - modelImporter.materialName = ModelImporterMaterialName.BasedOnMaterialName; - modelImporter.materialSearch = ModelImporterMaterialSearch.RecursiveUp; - } - - public void OnPreprocessMaterialDescription(MaterialDescription description, Material material, AnimationClip[] materialAnimation) - { - if (!ValidAsset(assetPath)) - { - return; - } - - M2Utility.M2 metadata = M2Utility.ReadMetadataFor(assetPath); - material = MaterialUtility.ConfigureMaterial(description, material, assetPath, metadata); + modelImporter.materialImportMode = ModelImporterMaterialImportMode.None; } public void OnPostprocessModel(GameObject gameObject) @@ -85,54 +78,35 @@ public void OnPostprocessModel(GameObject gameObject) return; } - GameObject physicsPrefab = AssetDatabase.LoadAssetAtPath(assetPath.Replace(".obj", ".phys.obj")); - MeshRenderer[] childRenderers = gameObject.GetComponentsInChildren(); - - if (physicsPrefab == null || physicsPrefab.GetComponentInChildren() == null) + if (!File.Exists(assetPath.Replace(".obj", ".phys.obj"))) { + MeshRenderer[] childRenderers = gameObject.GetComponentsInChildren(); foreach (MeshRenderer child in childRenderers) { child.gameObject.AddComponent(); } } - else - { - GameObject collider = new GameObject(); - collider.transform.SetParent(gameObject.transform); - collider.name = "Collision"; - MeshFilter collisionMesh = physicsPrefab.GetComponentInChildren(); - MeshCollider parentCollider = collider.AddComponent(); - parentCollider.sharedMesh = collisionMesh.sharedMesh; - } } static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { - string path; - - for(int i = 0; i < importedAssets.Length; i++) + var hasWow = false; + foreach (string path in importedAssets) { - path = importedAssets[i]; - - //M2 Utility Queue if (ValidAsset(path)) { - M2Utility.QueueMetadata(path); - } - - //ADT/WMO Item Collection Queue - if (Path.GetFileName(path).Contains("_ModelPlacementInformation.csv")) - { - ItemCollectionUtility.QueuePlacementData(path); + AssetConversionManager.QueuePostprocess(path); + hasWow = true; } + } - //ADT Liquid Volume Queue - if (Regex.IsMatch(path, @"liquid_\d{2}_\d{2}(?=\.json)")) + if (hasWow && !AssetConversionManager.IsBusy()) + { + var processNow = EditorUtility.DisplayDialog("WoW assets imported", "There were WoW assets imported, they need to be processed to work properly. Do you want to process them now? They can also be processed later by opening menu bar Window > wow.unity and clicking Process under All Assets.", "Process now", "Do it later"); + if (processNow) { - LiquidUtility.QueueLiquidData(path); + AssetConversionManager.JobPostprocessAllAssets(); } } - - EditorApplication.update += AssetConversionManager.ProcessAssets; } } diff --git a/Editor/WoWUnityWindow.cs b/Editor/WoWUnityWindow.cs new file mode 100644 index 0000000..b001c23 --- /dev/null +++ b/Editor/WoWUnityWindow.cs @@ -0,0 +1,147 @@ +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; +using WowUnity; + +public class WoWUnityWindow : EditorWindow +{ + private Dictionary selectedAssets; + + [MenuItem("Window/wow.unity")] + public static void ShowWindow() + { + GetWindow("wow.unity"); + } + + private void OnEnable() + { + Selection.selectionChanged += OnSelectionChangeForMap; + } + + private void OnDisable() + { + Selection.selectionChanged -= OnSelectionChangeForMap; + } + + void OnSelectionChangeForMap() + { + selectedAssets = new(); + foreach (var obj in Selection.objects) + { + if (obj.GetType() != typeof(GameObject)) + { + continue; + } + + string path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj); + if (!ADTUtility.IsAdtAny(path)) + { + continue; + } + + selectedAssets[obj.name] = obj as GameObject; + } + Repaint(); + } + + private void OnGUI() + { + GUILayout.Label("All Assets", EditorStyles.boldLabel); + if (GUILayout.Button("Process")) + { + ProcessAssets(); + } + + GUILayout.Space(10); + + GUILayout.Label("Map", EditorStyles.boldLabel); + + if (selectedAssets == null || selectedAssets.Count == 0) + { + GUILayout.Label("No tiles selected. Select some in the project window."); + } else + { + GUILayout.Label("Selected tiles:"); + + foreach (var asset in selectedAssets.Values) + { + EditorGUILayout.ObjectField(asset, typeof(GameObject), false); + } + + GUILayout.BeginHorizontal(); + + try + { + + if (GUILayout.Button("Setup Terrain")) + { + SetupTerrain(); + } + + if (GUILayout.Button("Place Doodads")) + { + PlaceDoodads(); + } + } catch (System.Exception) { + throw; + } finally + { + GUILayout.EndHorizontal(); + } + } + } + + void ProcessAssets() + { + AssetConversionManager.JobPostprocessAllAssets(); + } + + List GetRootAdtPaths() { + List paths = new(); + HashSet pathsH = new(); + + foreach (var selectedAsset in selectedAssets.Values) + { + string path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(selectedAsset); + if (pathsH.Contains(path)) + { + continue; + } + + pathsH.Add(path); + paths.Add(path); + } + + return paths; + } + + void SetupTerrain() + { + ADTUtility.PostProcessImports(GetRootAdtPaths()); + Debug.Log("Done setting up terrain."); + } + + void PlaceDoodads() + { + ProcessAssets(); + SetupTerrain(); + + foreach (var path in GetRootAdtPaths()) + { + GameObject prefab = M2Utility.FindPrefab(Path.ChangeExtension(path, "prefab")); + + TextAsset placementData = AssetDatabase.LoadAssetAtPath(Path.ChangeExtension(path, "obj").Replace(".obj", "_ModelPlacementInformation.csv")); + if (placementData == null) + { + Debug.LogWarning($"{path}: ModelPlacementInformation.csv not found."); + continue; + } + + Debug.Log($"{path}: placing doodads..."); + ItemCollectionUtility.PlaceModels(prefab, placementData); + } + + Debug.Log("Done placing doodads."); + } +} diff --git a/Editor/WoWUnityWindow.cs.meta b/Editor/WoWUnityWindow.cs.meta new file mode 100644 index 0000000..09ce765 --- /dev/null +++ b/Editor/WoWUnityWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7292a7181b93ea7449692ee99958c695 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: