Last active
February 9, 2020 18:08
-
-
Save gleblebedev/1a6dfd9ce58ad8e1b417abbd23f65ecf to your computer and use it in GitHub Desktop.
Unity to Urho3D scene convertor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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