using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using System.Xml; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; public class UrhoExporter:IDisposable { [MenuItem("Tools/Export Scene To Urho3D")] public static void ExportToUrho() { using (var exporter = new UrhoExporter(EditorSceneManager.GetActiveScene(), EditorUtility.SaveFilePanel( "Save scene as Urho XML", "", "scene.xml", "xml"))) { exporter.Export(); } } private Scene _scene; private int _id; string _outputFileName; private TextWriter _stream; private XmlTextWriter _writer; private string _assetsFolder; public UrhoExporter(Scene scene, string outputFileName) { _scene = scene; _outputFileName = Path.GetFullPath(outputFileName); _assetsFolder = Path.GetDirectoryName(Path.GetDirectoryName(_outputFileName)); if (string.IsNullOrEmpty(_assetsFolder)) _assetsFolder = Path.GetDirectoryName(_outputFileName); _stream = File.CreateText(_outputFileName); _writer = new XmlTextWriter(_stream); } public void Export() { if (string.IsNullOrEmpty(_outputFileName)) return; _writer.WriteStartDocument(); _writer.WriteWhitespace("\n"); _writer.WriteStartElement("scene"); _writer.WriteAttributeString("id", (++_id).ToString()); _writer.WriteWhitespace("\n"); var prefix = "\t"; StartCompoent(prefix, "Octree"); EndElement(prefix); StartCompoent(prefix, "DebugRenderer"); EndElement(prefix); StartCompoent(prefix, "PhysicsWorld"); EndElement(prefix); EnumerateObjects(prefix, _scene.GetRootGameObjects()); _writer.WriteEndElement(); _writer.WriteEndDocument(); } public void Dispose() { if (_stream != null) { _stream.Dispose(); } } private void EnumerateObjects(string prefix, GameObject[] objects) { foreach (var obj in objects) { WriteObject(prefix, obj); } } private string GetFileName(string name) { foreach (var invalidFileNameChar in Path.GetInvalidFileNameChars()) { name = name.Replace(invalidFileNameChar, '_'); } return name; } private void WriteObject(string prefix,GameObject obj) { _writer.WriteWhitespace(prefix); _writer.WriteStartElement("node"); _writer.WriteAttributeString("id", (++_id).ToString()); _writer.WriteWhitespace("\n"); var subPrefix = prefix + "\t"; var subSubPrefix = subPrefix + "\t"; WriteAttribute(subPrefix, "Is Enabled", obj.activeSelf); WriteAttribute(subPrefix, "Name", obj.name); WriteAttribute(subPrefix, "Tags", obj.tag); WriteAttribute(subPrefix, "Position", obj.transform.localPosition); WriteAttribute(subPrefix, "Rotation", obj.transform.localRotation); WriteAttribute(subPrefix, "Scale", obj.transform.localScale); var meshFilter = obj.GetComponent(); var meshRenderer = obj.GetComponent(); var meshCollider = obj.GetComponent(); var terrain = obj.GetComponent(); var light = obj.GetComponent(); var camera = obj.GetComponent(); if (camera != null) { StartCompoent(subPrefix, "Camera"); WriteAttribute(subSubPrefix, "Near Clip", camera.nearClipPlane); WriteAttribute(subSubPrefix, "Far Clip", camera.farClipPlane); EndElement(subPrefix); } if (light != null) { StartCompoent(subPrefix, "Light"); if (light.type == LightType.Directional) { WriteAttribute(subSubPrefix, "Light Type", "Directional"); } EndElement(subPrefix); } if (terrain != null) { var terrainSize = terrain.terrainData.size; var y = terrain.SampleHeight(new Vector3(0, 0, 0)); } if (meshRenderer != null) { if (meshFilter != null) { StartCompoent(subPrefix, "StaticModel"); var meshRelFileName = GetRelAssetPath(meshFilter.mesh); WriteAttribute(subSubPrefix, "Model", "Model;Models/" + meshRelFileName + ".mdl"); var meshFileName = Path.Combine(Path.Combine(_assetsFolder, "Models"), meshRelFileName + ".mdl"); if (!File.Exists(meshFileName)) { using (var writer = new BinaryWriter(File.Open(meshFileName, FileMode.Create, FileAccess.Write, FileShare.Read))) { WriteMesh(writer, meshFilter.mesh); } } StringBuilder material = new StringBuilder(); material.Append("Material"); var meshRendererMaterials = meshRenderer.materials; for (int i = 0; i < meshRendererMaterials.Length; ++i) { var meshRendererMaterial = meshRendererMaterials[i]; var relPath = GetRelAssetPath(meshRendererMaterial); var outputMaterialName = "Materials/" + relPath+".xml"; material.Append(";"); material.Append(outputMaterialName); var materialFileName = Path.Combine(_assetsFolder, outputMaterialName); if (!File.Exists(materialFileName)) { CreateMaterial(materialFileName, meshRendererMaterial); } } WriteAttribute(subSubPrefix, "Material", material.ToString()); EndElement(subPrefix); } } if (meshCollider != null) { } var transforms = obj.GetComponentsInChildren(true); for (int i = 0; i < transforms.Length; ++i) { if (transforms[i].gameObject != obj) WriteObject(subPrefix, transforms[i].gameObject); } _writer.WriteWhitespace(prefix); _writer.WriteEndElement(); _writer.WriteWhitespace("\n"); } public const uint Magic2 = 0x32444d55; private void WriteMesh(BinaryWriter writer, Mesh _mesh) { writer.Write(Magic2); writer.Write(1); for (int vbIndex = 0; vbIndex < 1 /*_mesh.vertexBufferCount*/; ++vbIndex) { var positions = _mesh.vertices; var normals = _mesh.normals; var colors = _mesh.colors; var tangents = _mesh.tangents; var uvs = _mesh.uv; var uvs2 = _mesh.uv2; var uvs3 = _mesh.uv3; var uvs4 = _mesh.uv4; writer.Write(positions.Length); var elements = new List(); if (positions.Length > 0) { elements.Add(new MeshVector3Stream(positions, VertexElementSemantic.SEM_POSITION)); } if (normals.Length > 0) { elements.Add(new MeshVector3Stream(normals, VertexElementSemantic.SEM_NORMAL)); } //if (colors.Length > 0) //{ // elements.Add(new MeshColorStream(colors, VertexElementSemantic.SEM_COLOR)); //} //if (tangents.Length > 0) //{ // elements.Add(new MeshVector4Stream(tangents, VertexElementSemantic.SEM_TANGENT)); //} if (uvs.Length > 0) { elements.Add(new MeshVector2Stream(uvs, VertexElementSemantic.SEM_TEXCOORD, 0)); } if (uvs2.Length > 0) { elements.Add(new MeshVector2Stream(uvs2, VertexElementSemantic.SEM_TEXCOORD, 1)); } if (uvs3.Length > 0) { elements.Add(new MeshVector2Stream(uvs2, VertexElementSemantic.SEM_TEXCOORD, 2)); } if (uvs4.Length > 0) { elements.Add(new MeshVector2Stream(uvs2, VertexElementSemantic.SEM_TEXCOORD, 3)); } writer.Write(elements.Count); for (int i = 0; i < elements.Count; ++i) { writer.Write(elements[i].Element); } int morphableVertexRangeStartIndex = 0; int morphableVertexCount = 0; writer.Write(morphableVertexRangeStartIndex); writer.Write(morphableVertexCount); for (int index = 0; index < positions.Length; ++index) { for (int i = 0; i < elements.Count; ++i) { elements[i].Write(writer, index); } } var indicesPerSubMesh = new List(); int totalIndices = 0; for (int subMeshIndex = 0; subMeshIndex < _mesh.subMeshCount; ++subMeshIndex) { var indices = _mesh.GetIndices(subMeshIndex); indicesPerSubMesh.Add(indices); totalIndices += indices.Length; } writer.Write(1); writer.Write(totalIndices); if (positions.Length < 65536) { writer.Write(2); for (int subMeshIndex = 0; subMeshIndex < _mesh.subMeshCount; ++subMeshIndex) { for (int i = 0; i < indicesPerSubMesh[subMeshIndex].Length; ++i) { writer.Write((ushort)indicesPerSubMesh[subMeshIndex][i]); } } } else { writer.Write(4); for (int subMeshIndex = 0; subMeshIndex < _mesh.subMeshCount; ++subMeshIndex) { for (int i = 0; i < indicesPerSubMesh[subMeshIndex].Length; ++i) { writer.Write(indicesPerSubMesh[subMeshIndex][i]); } } } writer.Write(indicesPerSubMesh.Count); totalIndices = 0; for (int subMeshIndex = 0; subMeshIndex < indicesPerSubMesh.Count; ++subMeshIndex) { var numberOfBoneMappingEntries = 0; writer.Write(numberOfBoneMappingEntries); var numberOfLODLevels = 1; writer.Write(numberOfLODLevels); writer.Write(0.0f); writer.Write((int)PrimitiveType.TRIANGLE_LIST); writer.Write(0); writer.Write(0); writer.Write(totalIndices); writer.Write(indicesPerSubMesh[subMeshIndex].Length); totalIndices += indicesPerSubMesh[subMeshIndex].Length; writer.Write(0); var numOfBones = 0; writer.Write(numOfBones); } float minX, minY, minZ; float maxX, maxY, maxZ; maxX = maxY = maxZ = float.MinValue; minX = minY = minZ = float.MaxValue; for (int i = 0; i < positions.Length; ++i) { if (minX > positions[i].x) minX = positions[i].x; if (minY > positions[i].y) minY = positions[i].y; if (minZ > positions[i].z) minZ = positions[i].z; if (maxX < positions[i].x) maxX = positions[i].x; if (maxY < positions[i].y) maxY = positions[i].y; if (maxZ < positions[i].z) maxZ = positions[i].z; } writer.Write(minX); writer.Write(minY); writer.Write(minZ); writer.Write(maxX); writer.Write(maxY); writer.Write(maxZ); } } public enum PrimitiveType { TRIANGLE_LIST = 0, LINE_LIST, POINT_LIST, TRIANGLE_STRIP, LINE_STRIP, TRIANGLE_FAN } public enum VertexElementType { TYPE_INT = 0, TYPE_FLOAT, TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4, TYPE_UBYTE4, TYPE_UBYTE4_NORM, MAX_VERTEX_ELEMENT_TYPES } public enum VertexElementSemantic { SEM_POSITION = 0, SEM_NORMAL, SEM_BINORMAL, SEM_TANGENT, SEM_TEXCOORD, SEM_COLOR, SEM_BLENDWEIGHTS, SEM_BLENDINDICES, SEM_OBJECTINDEX, MAX_VERTEX_ELEMENT_SEMANTICS } internal abstract class MeshStreamWriter { public int Element; public abstract void Write(BinaryWriter writer, int index); } internal class MeshVector3Stream: MeshStreamWriter { private Vector3[] positions; public MeshVector3Stream(Vector3[] positions, VertexElementSemantic sem, int index = 0) { this.positions = positions; Element = (int)VertexElementType.TYPE_VECTOR3 | ((int)sem << 8) | (index <<16); } public override void Write(BinaryWriter writer, int index) { writer.Write(positions[index].x); writer.Write(positions[index].y); writer.Write(positions[index].z); } } internal class MeshVector2Stream : MeshStreamWriter { private Vector2[] positions; public MeshVector2Stream(Vector2[] positions, VertexElementSemantic sem, int index = 0) { this.positions = positions; Element = (int)VertexElementType.TYPE_VECTOR2 | ((int)sem << 8) | (index << 16); } public override void Write(BinaryWriter writer, int index) { writer.Write(positions[index].x); writer.Write(positions[index].y); } } private void CreateMaterial(string materialFileName, Material material) { Directory.CreateDirectory(Path.GetDirectoryName(materialFileName)); using (var writer = XmlTextWriter.Create(materialFileName)) { writer.WriteStartDocument(); writer.WriteStartElement("material"); writer.WriteStartElement("technique"); writer.WriteAttributeString("name", "Techniques/Diff.xml"); writer.WriteAttributeString("quality", "0"); writer.WriteEndElement(); var shader = material.shader; for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++) { if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv) { var propertyName = ShaderUtil.GetPropertyName(shader, i); Texture texture = material.GetTexture(propertyName); if (texture != null) { switch (propertyName) { case "_MainTex": WriteTexture(texture, writer, "diffuse"); break; case "_SpecGlossMap": WriteTexture(texture, writer, "specular"); break; case "_ParallaxMap": break; case "_BumpMap": WriteTexture(texture, writer, "normal"); break; case "_DetailAlbedoMap": break; case "_DetailNormalMap": break; default: Debug.LogWarning(propertyName); break; } } } } writer.WriteEndElement(); writer.WriteEndDocument(); } } private void WriteTexture(Texture materialMainTexture, XmlWriter writer, string name) { if (materialMainTexture == null) return; var relPath = GetRelAssetPath(materialMainTexture); var dataPath = Application.dataPath; var outputPath = "Textures/" + relPath; writer.WriteStartElement("texture"); writer.WriteAttributeString("unit", name); writer.WriteAttributeString("name", outputPath); writer.WriteEndElement(); string destFileName = Path.Combine(this._assetsFolder, outputPath); if (!File.Exists(destFileName)) { Directory.CreateDirectory(Path.GetDirectoryName(destFileName)); File.Copy(Path.Combine(dataPath,relPath), destFileName); } } private string GetRelAssetPath(UnityEngine.Object assetObject) { var path = AssetDatabase.GetAssetPath(assetObject); if (string.IsNullOrEmpty(path)) return GetFileName(assetObject.name); var relPath = path.Substring(path.IndexOf('/') + 1); return relPath; } private void WriteAttribute(string prefix, string name, float pos) { WriteAttribute(prefix, name, string.Format(CultureInfo.InvariantCulture, "{0}", pos)); } private void WriteAttribute(string prefix,string name, Vector3 pos) { WriteAttribute(prefix, name, string.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", pos.x, pos.y, pos.z)); } private void WriteAttribute(string prefix, string name, Quaternion pos) { WriteAttribute(prefix, name, string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", pos.w, pos.x, pos.y, pos.z)); } private void WriteAttribute(string prefix, string name, bool flag) { WriteAttribute(prefix, name, flag ? "true" : "false"); } private void EndElement(string prefix) { _writer.WriteWhitespace(prefix); _writer.WriteEndElement(); _writer.WriteWhitespace("\n"); } private void StartCompoent(string prefix, string type) { _writer.WriteWhitespace(prefix); _writer.WriteStartElement("component"); _writer.WriteAttributeString("type", type); _writer.WriteAttributeString("id", (++_id).ToString()); _writer.WriteWhitespace("\n"); } private void WriteAttribute(string prefix, string name, string vaue) { _writer.WriteWhitespace(prefix); _writer.WriteStartElement("attribute"); _writer.WriteAttributeString("name", name); _writer.WriteAttributeString("value", vaue); _writer.WriteEndElement(); _writer.WriteWhitespace("\n"); } }