Skip to content

Instantly share code, notes, and snippets.

@gleblebedev
Last active February 9, 2020 18:08
Show Gist options
  • Save gleblebedev/1a6dfd9ce58ad8e1b417abbd23f65ecf to your computer and use it in GitHub Desktop.
Save gleblebedev/1a6dfd9ce58ad8e1b417abbd23f65ecf to your computer and use it in GitHub Desktop.
Unity to Urho3D scene convertor
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
public class UrhoExporter : IDisposable
{
public enum PrimitiveType
{
TRIANGLE_LIST = 0,
LINE_LIST,
POINT_LIST,
TRIANGLE_STRIP,
LINE_STRIP,
TRIANGLE_FAN
}
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
}
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 DXGI_FORMAT
{
Unknown = 0,
DXGI_FORMAT_R8G8B8A8_SINT = 32,
DXGI_FORMAT_BC3_UNORM = 77,
}
public const uint Magic2 = 0x32444d55;
public static Technique[] Techniques =
{
new Technique {Material = new MaterialFlags(), Name = "NoTexture.xml"},
new Technique {Material = new MaterialFlags {hasAlpha = true}, Name = "NoTextureAlpha.xml"},
new Technique {Material = new MaterialFlags {hasNormal = true}, Name = "NoTextureNormal.xml"},
new Technique
{
Material = new MaterialFlags {hasNormal = true, hasAlpha = true},
Name = "NoTextureNormalAlpha.xml"
},
//new Technique
//{
// Material = new MaterialFlags {hasNormal = true, hasAlpha = true, hasEmissive = true},
// Name = "NoTextureNormalEmissiveAlpha.xml"
//},
new Technique {Material = new MaterialFlags {hasDiffuse = true}, Name = "Diff.xml"},
new Technique {Material = new MaterialFlags {hasDiffuse = true, hasAlpha = true}, Name = "DiffAlpha.xml"},
new Technique {Material = new MaterialFlags {hasDiffuse = true, hasSpecular = true}, Name = "DiffSpec.xml"},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasSpecular = true, hasAlpha = true},
Name = "DiffSpecAlpha.xml"
},
new Technique {Material = new MaterialFlags {hasDiffuse = true, hasNormal = true}, Name = "DiffNormal.xml"},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasNormal = true, hasAlpha = true},
Name = "DiffNormalAlpha.xml"
},
new Technique {Material = new MaterialFlags {hasDiffuse = true, hasEmissive = true}, Name = "DiffEmissive.xml"},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasEmissive = true, hasAlpha = true},
Name = "DiffEmissiveAlpha.xml"
},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasSpecular = true, hasNormal = true},
Name = "DiffNormalSpec.xml"
},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasSpecular = true, hasNormal = true, hasAlpha = true},
Name = "DiffNormalSpecAlpha.xml"
},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasEmissive = true, hasNormal = true},
Name = "DiffNormalEmissive.xml"
},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasEmissive = true, hasNormal = true, hasAlpha = true},
Name = "DiffNormalEmissiveAlpha.xml"
},
new Technique
{
Material = new MaterialFlags {hasDiffuse = true, hasSpecular = true, hasNormal = true, hasEmissive = true},
Name = "DiffNormalSpecEmissive.xml"
},
new Technique
{
Material = new MaterialFlags
{
hasDiffuse = true,
hasSpecular = true,
hasNormal = true,
hasEmissive = true,
hasAlpha = true
},
Name = "DiffNormalSpecEmissiveAlpha.xml"
}
};
private readonly string _assetsFolder;
private readonly string _outputFileName;
private readonly TextWriter _stream;
private readonly XmlTextWriter _writer;
private readonly string _fileNameWithoutExtension;
private int _id;
private Scene _scene;
public UrhoExporter(Scene scene, string outputFileName)
{
_scene = scene;
_outputFileName = Path.GetFullPath(outputFileName);
_fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_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 Dispose()
{
if (_stream != null)
_stream.Dispose();
}
[MenuItem("Tools/Export Scene To Urho3D")]
public static void ExportToUrho()
{
var activeScene = SceneManager.GetActiveScene();
using (var exporter = new UrhoExporter(activeScene, EditorUtility.SaveFilePanel(
"Save scene as Urho XML",
"",
activeScene.name + ".xml",
"xml")))
{
exporter.Export();
}
}
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(), new HashSet<Renderer>());
WriteAttribute(prefix, "Next Replicated Node ID", _id);
WriteAttribute(prefix, "Next Replicated Component ID", _id);
_writer.WriteEndElement();
_writer.WriteEndDocument();
}
private void EnumerateObjects(string prefix, GameObject[] objects, HashSet<Renderer> excludeList)
{
foreach (var obj in objects)
WriteObject(prefix, obj, excludeList);
}
private string GetFileName(string name)
{
foreach (var invalidFileNameChar in Path.GetInvalidFileNameChars())
name = name.Replace(invalidFileNameChar, '_');
return name;
}
private void WriteObject(string prefix, GameObject obj, HashSet<Renderer> excludeList)
{
var localExcludeList = new HashSet<Renderer>(excludeList);
_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 lodGroup = obj.GetComponent<LODGroup>();
var meshCollider = obj.GetComponent<MeshCollider>();
var terrain = obj.GetComponent<Terrain>();
var light = obj.GetComponent<Light>();
var camera = obj.GetComponent<Camera>();
var reflectionProbe = obj.GetComponent<ReflectionProbe>();
if (reflectionProbe != null)
{
StartCompoent(subPrefix, "Zone");
WriteAttribute(subSubPrefix, "Bounding Box Min", -(reflectionProbe.size*0.5f));
WriteAttribute(subSubPrefix, "Bounding Box Max", (reflectionProbe.size * 0.5f));
var cubemap = reflectionProbe.bakedTexture as Cubemap;
if (cubemap != null)
{
var name = SaveCubemap(cubemap);
WriteAttribute(subSubPrefix, "Zone Texture", "TextureCube;"+name);
}
EndElement(subPrefix);
}
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);
WriteAttribute(subSubPrefix, "Cast Shadows", light.shadows != LightShadows.None);
EndElement(subPrefix);
}
if (terrain != null)
{
var terrainSize = terrain.terrainData.size;
_writer.WriteWhitespace(subPrefix);
_writer.WriteStartElement("node");
_writer.WriteAttributeString("id", (++_id).ToString());
_writer.WriteWhitespace("\n");
var w = terrain.terrainData.heightmapWidth;
var h = terrain.terrainData.heightmapHeight;
float max = float.MinValue;
float min = float.MaxValue;
var heights = terrain.terrainData.GetHeights(0, 0, w, h);
foreach (var height in heights)
{
if (height > max) max = height;
if (height < min) min = height;
}
if (max < min)
{
max = 1;
min = 0;
}
else if (max == min)
{
max = min + 0.1f;
}
WriteAttribute(subPrefix, "Position", new Vector3(terrainSize.x * 0.5f, -min, terrainSize.z * 0.5f));
StartCompoent(subPrefix, "Terrain");
var folderAndName = _fileNameWithoutExtension + "/" +
Path.GetInvalidFileNameChars().Aggregate(obj.name, (_1, _2) => _1.Replace(_2, '_'));
var heightmapFileName = "Textures/Terrains/" + folderAndName + ".tga";
var materialFileName = "Materials/Terrains/" + folderAndName + ".xml";
WriteAttribute(subSubPrefix, "Height Map", "Image;" + heightmapFileName);
WriteAttribute(subSubPrefix, "Material", "Material;"+ materialFileName);
WriteTerrainMaterial(terrain, Path.Combine(_assetsFolder, materialFileName));
WriteAttribute(subSubPrefix, "Vertex Spacing", new Vector3(terrainSize.x / w, (max - min), terrainSize.z / h));
Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(_assetsFolder, heightmapFileName)));
using (var imageFile = File.Open(Path.Combine(_assetsFolder, heightmapFileName), FileMode.Create, FileAccess.Write,
FileShare.Read))
{
using (var binaryWriter = new BinaryWriter(imageFile))
{
binaryWriter.Write((byte)0);
binaryWriter.Write((byte)0);
binaryWriter.Write((byte)3);
binaryWriter.Write((short)0);
binaryWriter.Write((short)0);
binaryWriter.Write((byte)0);
binaryWriter.Write((short)0);
binaryWriter.Write((short)0);
binaryWriter.Write((short)w);
binaryWriter.Write((short)h);
binaryWriter.Write((byte)8);
binaryWriter.Write((byte)0);
for (int y = h - 1; y >= 0; --y)
{
for (int x = 0; x < w; ++x)
{
var height = (heights[w - x - 1, y] - min) / (max - min) * 255.0f;
binaryWriter.Write((byte)height);
}
}
}
}
//WriteMaterialAttribute(subSubPrefix, terrain.terrainData.splatPrototypes);
EndElement(subPrefix);
EndElement(subPrefix);
}
if (lodGroup != null)
{
var lods = lodGroup.GetLODs();
foreach (var lod in lods.Skip(1))
{
foreach (var renderer in lod.renderers)
{
localExcludeList.Add(renderer);
}
}
//lods[0].renderers
}
if (meshRenderer != null && !localExcludeList.Contains(meshRenderer))
if (meshFilter != null)
{
StartCompoent(subPrefix, "StaticModel");
var sharedMesh = meshFilter.sharedMesh;
var meshRelFileName = GetRelAssetPath(sharedMesh);
var subObjectPath = GetMeshPath(meshFilter);
if (!string.IsNullOrEmpty(subObjectPath))
meshRelFileName = meshRelFileName + "/" + subObjectPath;
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, sharedMesh);
}
}
}
WriteMaterialAttribute(subSubPrefix, meshRenderer.sharedMaterials);
WriteAttribute(subSubPrefix, "Cast Shadows", meshRenderer.shadowCastingMode != ShadowCastingMode.Off);
EndElement(subPrefix);
}
if (meshCollider != null)
{
}
foreach (Transform childTransform in obj.transform)
if (childTransform.parent.gameObject == obj)
WriteObject(subPrefix, childTransform.gameObject, localExcludeList);
_writer.WriteWhitespace(prefix);
_writer.WriteEndElement();
_writer.WriteWhitespace("\n");
}
private string SaveCubemap(Cubemap cubemap)
{
var path = "Textures/"+GetRelAssetPath(cubemap);
var basefileName = Path.Combine(_assetsFolder, path);
Directory.CreateDirectory(Path.GetDirectoryName(basefileName));
using (var writer = XmlWriter.Create(basefileName + ".xml"))
{
writer.WriteStartDocument();
writer.WriteStartElement("cubemap");
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("face");
writer.WriteAttributeString("name", Path.GetFileName(path)+ "_PosX.png");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("face");
writer.WriteAttributeString("name", Path.GetFileName(path) + "_NegX.png");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("face");
writer.WriteAttributeString("name", Path.GetFileName(path) + "_PosY.png");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("face");
writer.WriteAttributeString("name", Path.GetFileName(path) + "_NegY.png");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("face");
writer.WriteAttributeString("name", Path.GetFileName(path) + "_PosZ.png");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("face");
writer.WriteAttributeString("name", Path.GetFileName(path) + "_NegZ.png");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteEndElement();
writer.WriteEndDocument();
}
Texture2D tex;
byte[] bytes;
tex = new Texture2D(cubemap.width, cubemap.height, TextureFormat.RGB24, false);
try
{
tex.SetPixels(cubemap.GetPixels(CubemapFace.PositiveX));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Path.Combine(_assetsFolder, path + "_PosX.png"), bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.NegativeX));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Path.Combine(_assetsFolder, path + "_NegX.png"), bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.PositiveY));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Path.Combine(_assetsFolder, path + "_PosY.png"), bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.NegativeY));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Path.Combine(_assetsFolder, path + "_NegY.png"), bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.PositiveZ));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Path.Combine(_assetsFolder, path + "_PosZ.png"), bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.NegativeZ));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Path.Combine(_assetsFolder, path + "_NegZ.png"), bytes);
}
catch (Exception ex)
{
Debug.LogError(ex);
}
Object.DestroyImmediate(tex);
return path +".xml";
}
private void WriteMaterialAttribute(string subSubPrefix, Material[] meshRendererMaterials)
{
var material = new StringBuilder();
material.Append("Material");
for (var 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());
}
private void WriteMesh(BinaryWriter writer, Mesh _mesh)
{
writer.Write(Magic2);
writer.Write(1);
for (var 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, 0));
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 (var i = 0; i < elements.Count; ++i)
writer.Write(elements[i].Element);
var morphableVertexRangeStartIndex = 0;
var morphableVertexCount = 0;
writer.Write(morphableVertexRangeStartIndex);
writer.Write(morphableVertexCount);
for (var index = 0; index < positions.Length; ++index)
for (var i = 0; i < elements.Count; ++i)
elements[i].Write(writer, index);
var indicesPerSubMesh = new List<int[]>();
var totalIndices = 0;
for (var 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 (var subMeshIndex = 0; subMeshIndex < _mesh.subMeshCount; ++subMeshIndex)
for (var i = 0; i < indicesPerSubMesh[subMeshIndex].Length; ++i)
writer.Write((ushort)indicesPerSubMesh[subMeshIndex][i]);
}
else
{
writer.Write(4);
for (var subMeshIndex = 0; subMeshIndex < _mesh.subMeshCount; ++subMeshIndex)
for (var i = 0; i < indicesPerSubMesh[subMeshIndex].Length; ++i)
writer.Write(indicesPerSubMesh[subMeshIndex][i]);
}
writer.Write(indicesPerSubMesh.Count);
totalIndices = 0;
for (var 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 (var 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);
}
}
private void WriteTerrainMaterial(Terrain terrain, string materialFileName)
{
SplatPrototype[] textures = terrain.terrainData.splatPrototypes;
Directory.CreateDirectory(Path.GetDirectoryName(materialFileName));
using (var writer = XmlWriter.Create(materialFileName))
{
writer.WriteStartDocument();
writer.WriteStartElement("material");
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("technique");
writer.WriteAttributeString("name", "Techniques/Diff.xml");
writer.WriteAttributeString("quality", "0");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("parameter");
writer.WriteAttributeString("name", "UOffset");
writer.WriteAttributeString("value", Format(new Vector4(terrain.terrainData.size.x,0,0,0)));
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("parameter");
writer.WriteAttributeString("name", "VOffset");
writer.WriteAttributeString("value", Format(new Vector4(0, terrain.terrainData.size.z, 0, 0)));
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
WriteTexture(textures[0].texture, writer, "diffuse");
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private void CreateMaterial(string materialFileName, Material material)
{
Directory.CreateDirectory(Path.GetDirectoryName(materialFileName));
using (var writer = XmlWriter.Create(materialFileName))
{
writer.WriteStartDocument();
writer.WriteStartElement("material");
writer.WriteWhitespace(Environment.NewLine);
var matDiffColor = Color.white;
var matSpecColor = new Color(0, 0, 0, 0);
var matEmissiveColor = Color.black;
var flags = new MaterialFlags();
flags.hasAlpha = material.renderQueue == (int)RenderQueue.Transparent;
var shader = material.shader;
for (var i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)
{
var propertyName = ShaderUtil.GetPropertyName(shader, i);
if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
{
var texture = material.GetTexture(propertyName);
if (texture != null)
switch (propertyName)
{
case "_Diffuse":
case "_MainTex":
flags.hasDiffuse = true;
WriteTexture(texture, writer, "diffuse");
break;
case "_SpecGlossMap":
flags.hasSpecular = true;
WriteTexture(texture, writer, "specular");
break;
case "_ParallaxMap":
break;
case "_Normal":
case "_BumpMap":
flags.hasNormal = true;
WriteTexture(texture, writer, "normal", true);
break;
case "_DetailAlbedoMap":
break;
case "_DetailNormalMap":
break;
case "_EmissionMap":
flags.hasEmissive = true;
WriteTexture(texture, writer, "emissive");
break;
case "_MetallicGlossMap":
break;
case "_OcclusionMap":
break;
case "_Mask":
break;
case "_DetailMask":
break;
default:
Debug.LogWarning(propertyName);
break;
}
}
else if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.Color)
{
var color = material.GetColor(propertyName);
switch (propertyName)
{
case "_FresnelColor":
break;
case "_MainColor":
case "_Color":
matDiffColor = color;
break;
case "_EmissionColor":
matEmissiveColor = color;
break;
case "_SpecColor":
matSpecColor = color;
break;
default:
Debug.LogWarning(propertyName);
break;
}
}
else if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.Range)
{
var value = material.GetFloat(propertyName);
switch (propertyName)
{
case "_Cutoff":
case "_Glossiness":
case "_GlossMapScale":
case "_Parallax":
case "_OcclusionStrength":
case "_Specular":
case "_Gloss":
case "_FresnelPower":
case "_FresnelExp":
case "_Alpha_2":
case "_RefractionPower":
break;
case "_Alpha_1":
matDiffColor.a = value;
break;
default:
Debug.LogWarning(propertyName);
break;
}
}
else if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.Float)
{
var value = material.GetFloat(propertyName);
switch (propertyName)
{
case "_SmoothnessTextureChannel":
case "_SpecularHighlights":
case "_GlossyReflections":
case "_BumpScale":
case "_DetailNormalMapScale":
case "_UVSec":
case "_Mode":
case "_SrcBlend":
case "_DstBlend":
case "_ZWrite":
break;
default:
Debug.LogWarning(propertyName);
break;
}
}
}
if (!flags.hasDiffuse)
WriteParameter(writer, "\t", "MatDiffColor", Format(matDiffColor));
if (!flags.hasSpecular)
WriteParameter(writer, "\t", "MatSpecColor", Format(matSpecColor));
if (!flags.hasEmissive)
WriteParameter(writer, "\t", "MatEmissiveColor", Format(matEmissiveColor));
writer.WriteWhitespace(Environment.NewLine);
writer.WriteStartElement("technique");
var bestTechnique = Techniques[0];
var bestTechniqueDistance = bestTechnique.Material - flags;
foreach (var technique in Techniques)
if (technique.Material.Fits(flags))
{
var d = technique.Material - flags;
if (d < bestTechniqueDistance)
{
bestTechnique = technique;
bestTechniqueDistance = d;
}
}
writer.WriteAttributeString("name", "Techniques/" + bestTechnique.Name);
writer.WriteAttributeString("quality", "0");
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private void WriteTexture(Texture texture, XmlWriter writer, string name, bool isNormal = false,
bool canCompress = true)
{
writer.WriteStartElement("texture");
writer.WriteAttributeString("unit", name);
writer.WriteAttributeString("name", WriteTexture(texture,isNormal,canCompress));
writer.WriteEndElement();
writer.WriteWhitespace(Environment.NewLine);
}
private string WriteTexture(Texture texture, bool isNormal = false, bool canCompress = true)
{
if (texture == null)
return null;
var sourceTexture2D = texture as Texture2D;
var relPath = GetRelAssetPath(texture);
var extIndex = relPath.LastIndexOf('.') + 1;
var needFormatConvertion = false;
if (extIndex > 0)
{
var ext = relPath.Substring(extIndex).ToLower();
if (sourceTexture2D != null)
{
if (canCompress)// && ext != "tga")
{
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;
var destFileName = Path.Combine(_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);
br.Write(new byte[]
{
0x00, 0x00, 0x40, 0x00, //dwPitchOrLinearSize
0x00, 0x00, 0x00, 0x00, //dwDepth
});
int dwMipMapCount = texture2d.mipmapCount;
br.Write(dwMipMapCount);
br.Write(new byte[]
{
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, //DDS_PIXELFORMAT dwSize
});
int pixelFormatFlags = 4;
if (texture2d.alphaIsTransparency)
pixelFormatFlags |= 1;
br.Write(pixelFormatFlags);
if (texture2d.format == TextureFormat.DXT5)
br.Write(new byte[] { 0x44, 0x58, 0x54, 0x35 }); //dwFourCC
else
br.Write(new byte[] { 0x44, 0x58, 0x54, 0x31 }); //dwFourCC
br.Write(new byte[]
{
0x00, 0x00, 0x00, 0x00, //dwRGBBitCount
0x00, 0x00, 0x00, 0x00, //dwRBitMask
0x00, 0x00, 0x00, 0x00, //dwGBitMask
0x00, 0x00, 0x00, 0x00, //dwBBitMask
0x00, 0x00, 0x00, 0x00, //dwABitMask
});
int caps = 0x00400008;
if (texture2d.alphaIsTransparency)
caps |= 0x00001000;
br.Write(caps);
br.Write(new byte[]
{
0x00, 0x00, 0x00, 0x00, //dwCaps2
0x00, 0x00, 0x00, 0x00, //dwCaps3
0x00, 0x00, 0x00, 0x00, //dwCaps4
0x00, 0x00, 0x00, 0x00, //dwReserved2
});
br.Write(rawCompressedTexture);
File.WriteAllBytes(destFileName, ms.ToArray());
//var rawCompressedTexture = texture2d.EncodeToPNG();
//File.WriteAllBytes(destFileName, rawCompressedTexture);
Object.DestroyImmediate(texture2d);
}
else
{
File.Copy(Path.Combine(dataPath, relPath), destFileName);
}
}
return outputPath;
}
private string GetRelAssetPath(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 GetMeshPath(MeshFilter prefabFilter)
{
var modelPath = AssetDatabase.GetAssetPath(prefabFilter.sharedMesh);
if (string.IsNullOrEmpty(modelPath))
return null;
var model = AssetDatabase.LoadAssetAtPath<GameObject>(modelPath);
if (!model)
{
Debug.LogWarning("No game object found at " + modelPath, prefabFilter);
return null;
}
MeshFilter modelFilter = null;
var filters = model.GetComponentsInChildren<MeshFilter>(true);
foreach (var filter in filters)
if (filter.sharedMesh == prefabFilter.sharedMesh)
{
modelFilter = filter;
break;
}
if (!modelFilter)
{
Debug.LogWarning("No game object found for " + prefabFilter, prefabFilter);
return null;
}
if (filters.Length == 1)
return null;
var transform = modelFilter.transform;
var path = transform.name;
//while (transform.parent)
//{
// transform = transform.parent;
// path = transform.name + "/" + path;
//}
return path;
}
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, Vector4 pos)
{
WriteAttribute(prefix, name, Format(pos));
}
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(_writer, prefix, name, Format(pos));
}
private void WriteAttribute(XmlWriter writer, string prefix, string name, Color pos)
{
WriteAttribute(writer, prefix, name, Format(pos));
}
private static string Format(Color pos)
{
return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", pos.r, pos.g, pos.b, pos.a);
}
private static string Format(Vector4 pos)
{
return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", pos.x, pos.y, pos.z, pos.w);
}
private void WriteAttribute(string prefix, string name, bool flag)
{
WriteAttribute(prefix, name, flag ? "true" : "false");
}
private void WriteAttribute(string prefix, string name, int flag)
{
WriteAttribute(prefix, name, flag.ToString(CultureInfo.InvariantCulture));
}
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)
{
WriteAttribute(_writer, prefix, name, vaue);
}
private void WriteAttribute(XmlWriter writer, 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");
}
private void WriteParameter(XmlWriter writer, string prefix, string name, string vaue)
{
writer.WriteWhitespace(prefix);
writer.WriteStartElement("parameter");
writer.WriteAttributeString("name", name);
writer.WriteAttributeString("value", vaue);
writer.WriteEndElement();
writer.WriteWhitespace("\n");
}
public class MaterialFlags
{
public bool hasAlpha;
public bool hasDiffuse;
public bool hasEmissive;
public bool hasNormal;
public bool hasSpecular;
public static int operator -(MaterialFlags a, MaterialFlags b)
{
return GetDistance(a.hasDiffuse, b.hasDiffuse) + GetDistance(a.hasSpecular, b.hasSpecular) +
GetDistance(a.hasNormal, b.hasNormal) + GetDistance(a.hasEmissive, b.hasEmissive) +
GetDistance(a.hasAlpha, b.hasAlpha);
}
private static int GetDistance(bool a, bool b)
{
return a != b ? 1 : 0;
}
public bool Fits(MaterialFlags b)
{
return (!hasDiffuse || b.hasDiffuse)
&& (!hasSpecular || b.hasSpecular)
&& (!hasEmissive || b.hasEmissive)
&& (!hasNormal || b.hasNormal)
&& hasAlpha == b.hasAlpha;
}
}
public class Technique
{
public MaterialFlags Material;
public string Name;
}
internal abstract class MeshStreamWriter
{
public int Element;
public abstract void Write(BinaryWriter writer, int index);
}
internal class MeshVector3Stream : MeshStreamWriter
{
private readonly 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 readonly 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);
}
}
internal class MeshVector4Stream : MeshStreamWriter
{
private readonly Vector4[] positions;
public MeshVector4Stream(Vector4[] positions, VertexElementSemantic sem, int index = 0)
{
this.positions = positions;
Element = (int)VertexElementType.TYPE_VECTOR4 | ((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);
writer.Write(positions[index].w);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment