Skip to content

Instantly share code, notes, and snippets.

@elix22
Forked from gleblebedev/UrhoExporter.cs
Created June 21, 2017 23:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elix22/155cce99daba3d60cb983a07c65aa626 to your computer and use it in GitHub Desktop.
Save elix22/155cce99daba3d60cb983a07c65aa626 to your computer and use it in GitHub Desktop.
Unity to Urho3D scene convertor
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<MeshFilter>();
var meshRenderer = obj.GetComponent<MeshRenderer>();
var meshCollider = obj.GetComponent<MeshCollider>();
var terrain = obj.GetComponent<Terrain>();
var light = obj.GetComponent<Light>();
var camera = obj.GetComponent<Camera>();
if (camera != null)
{
StartCompoent(subPrefix, "Camera");
WriteAttribute(subSubPrefix, "Near Clip", camera.nearClipPlane);
WriteAttribute(subSubPrefix, "Far Clip", camera.farClipPlane);
EndElement(subPrefix);
}
if (light != null && light.type != LightType.Area)
{
StartCompoent(subPrefix, "Light");
if (light.type == LightType.Directional)
{
WriteAttribute(subSubPrefix, "Light Type", "Directional");
}
else if (light.type == LightType.Spot)
{
WriteAttribute(subSubPrefix, "Light Type", "Spot");
}
else if (light.type == LightType.Point)
{
WriteAttribute(subSubPrefix, "Range", light.range);
}
WriteAttribute(subSubPrefix, "Color", light.color);
WriteAttribute(subSubPrefix, "Brightness Multiplier", light.intensity);
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.sharedMesh);
WriteAttribute(subSubPrefix, "Model", "Model;Models/" + meshRelFileName + ".mdl");
var meshFileName = Path.Combine(Path.Combine(_assetsFolder, "Models"), meshRelFileName + ".mdl");
if (!File.Exists(meshFileName))
{
Directory.CreateDirectory(Path.GetDirectoryName(meshFileName));
using (var fileStream = File.Open(meshFileName, FileMode.Create, FileAccess.Write, FileShare.Read))
{
using (var writer = new BinaryWriter(fileStream))
{
WriteMesh(writer, meshFilter.sharedMesh);
}
}
}
StringBuilder material = new StringBuilder();
material.Append("Material");
var meshRendererMaterials = meshRenderer.sharedMaterials;
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)
{
}
foreach (Transform childTransform in obj.transform)
{
if (childTransform.parent.gameObject == obj)
WriteObject(subPrefix, childTransform.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<MeshStreamWriter>();
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[]>();
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;
case "_EmissionMap":
break;
case "_MetallicGlossMap":
break;
case "_OcclusionMap":
break;
case "_DetailMask":
break;
default:
Debug.LogWarning(propertyName);
break;
}
}
}
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private string WriteTexture(Texture texture, XmlWriter writer, string name)
{
if (texture == null)
return null;
var sourceTexture2D = texture as Texture2D;
var relPath = GetRelAssetPath(texture);
int extIndex = relPath.LastIndexOf('.')+1;
bool needFormatConvertion = false;
if (extIndex > 0)
{
string ext = relPath.Substring(extIndex).ToLower();
if (sourceTexture2D != null)
{
if (ext == "psd" || sourceTexture2D.format == TextureFormat.DXT5 || sourceTexture2D.format == TextureFormat.DXT1)
{
needFormatConvertion = true;
relPath = relPath.Substring(0, extIndex) + "dds";
}
}
}
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));
if (needFormatConvertion)
{
//var texture2d = new Texture2D(sourceTexture.width, sourceTexture.height);
//texture2d.SetPixels32(sourceTexture.GetPixels32(0));
var texture2d = new Texture2D(sourceTexture2D.width, sourceTexture2D.height, sourceTexture2D.format, sourceTexture2D.mipmapCount > 1);
texture2d.LoadRawTextureData(sourceTexture2D.GetRawTextureData());
texture2d.Apply();
texture2d.Compress(true);
var rawCompressedTexture = texture2d.GetRawTextureData();
var ms = new MemoryStream();
var br = new BinaryWriter(ms);
var a = new byte[]
{
0x44, 0x44, 0x53, 0x20, 0x7C, 0x00, 0x00, 0x00, 0x07, 0x10, 0x0A, 0x00,
};
br.Write(a);
br.Write(sourceTexture2D.width);
br.Write(sourceTexture2D.height);
a = new byte[]
{
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
};
br.Write(a);
if (texture2d.format == TextureFormat.DXT5)
a = new byte[] { 0x44, 0x58, 0x54, 0x35 };
else
a = new byte[] { 0x44, 0x58, 0x54, 0x31 };
br.Write(a);
a = new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
br.Write(a);
br.Write(rawCompressedTexture);
File.WriteAllBytes(destFileName, ms.ToArray());
//var rawCompressedTexture = texture2d.EncodeToPNG();
//File.WriteAllBytes(destFileName, rawCompressedTexture);
UnityEngine.Object.Destroy(texture2d);
}
else
{
File.Copy(Path.Combine(dataPath, relPath), destFileName);
}
}
return relPath;
}
private string GetRelAssetPath(UnityEngine.Object assetObject)
{
var path = AssetDatabase.GetAssetPath(assetObject);
if (string.IsNullOrEmpty(path))
return GetFileName(TrimInstance(assetObject.name));
var relPath = path.Substring(path.IndexOf('/') + 1);
return relPath;
}
private string TrimInstance(string assetObjectName)
{
var instance = " Instance";
var instance2 = " (Instance)";
loop:
if (assetObjectName.EndsWith(instance))
{
assetObjectName = assetObjectName.Substring(0, assetObjectName.Length - instance.Length);
goto loop;
}
if (assetObjectName.EndsWith(instance2))
{
assetObjectName = assetObjectName.Substring(0, assetObjectName.Length - instance2.Length);
goto loop;
}
return assetObjectName;
}
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, Color pos)
{
WriteAttribute(prefix, name, string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", pos.r, pos.g, pos.b, pos.a));
}
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");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment