Skip to content
147 changes: 147 additions & 0 deletions Editor/ADTUtility.cs
Original file line number Diff line number Diff line change
@@ -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<string> 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<GameObject>(path);
Renderer[] renderers = imported.GetComponentsInChildren<Renderer>();
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<Renderer>();

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<Tex>(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<Layer> 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<int> DoodadID;
public List<float> DoodadWeight;
public Dictionary<string, EffectModel> DoodadModelIDs;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

216 changes: 210 additions & 6 deletions Editor/AssetConversionManager.cs
Original file line number Diff line number Diff line change
@@ -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<string> importedModelPathQueue = new();
private static readonly ConcurrentQueue<string> importedWMOPathQueue = new();
private static readonly ConcurrentQueue<string> 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<TextAsset>(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<GameObject>(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<MeshFilter>();
if (collisionMesh == null)
{
return;
}

var prefabInst = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
prefabInst.GetComponentsInChildren<MeshCollider>().ToList().ForEach(collider => Object.DestroyImmediate(collider));

GameObject collider = new();
collider.transform.SetParent(prefabInst.transform);
collider.name = "Collision";
MeshCollider parentCollider = collider.AddComponent<MeshCollider>();
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();
}
}
}
Loading