398 lines
11 KiB
C#

using System;
using System.IO;
using UnityEngine;
// A simple mesh saving/loading functionality.
// This is an utility script, you don't need to add it to any objects.
// See SaveMeshForWeb and LoadMeshFromWeb for example of use.
//
// Uses a custom binary format:
//
// 2 bytes vertex count
// 2 bytes triangle count
// 1 bytes vertex format (bits: 0=vertices, 1=normals, 2=tangents, 3=uvs)
//
// After that come vertex component arrays, each optional except for positions.
// Which ones are present depends on vertex format:
// Positions
// Bounding box is before the array (xmin,xmax,ymin,ymax,zmin,zmax)
// Then each vertex component is 2 byte unsigned short, interpolated between the bound axis
// Normals
// One byte per component
// Tangents
// One byte per component
// UVs (8 bytes/vertex - 2 floats)
// Bounding box is before the array (xmin,xmax,ymin,ymax)
// Then each UV component is 2 byte unsigned short, interpolated between the bound axis
//
// Finally the triangle indices array: 6 bytes per triangle (3 unsigned short indices)
[Serializable]
public class MeshSerializer : MonoBehaviour
{
// Reads mesh from an array of bytes. Can return null
// if the bytes seem invalid.
public static Mesh ReadMesh(byte[] bytes)
{
if (bytes == null || bytes.Length < 5)
{
print("Invalid mesh file!");
return null;
}
BinaryReader buf = new BinaryReader(new MemoryStream(bytes));
// read header
UInt16 vertCount = buf.ReadUInt16();
UInt16 triCount = buf.ReadUInt16();
byte format = buf.ReadByte();
// sanity check
if ((int)vertCount < 0 || (int)vertCount > 64000)
{
print("Invalid vertex count in the mesh data!");
return null;
}
if ((int)triCount < 0 || (int)triCount > 64000)
{
print("Invalid triangle count in the mesh data!");
return null;
}
if ((int)format < 1 || ((int)format & 1) == 0 || (int)format > 15)
{
print("Invalid vertex format in the mesh data!");
return null;
}
Mesh mesh = new Mesh();
int i;
// positions
Vector3[] verts = new Vector3[(int)vertCount];
ReadVector3Array16bit(verts, buf);
mesh.vertices = verts;
if (((int)format & 2) != 0) // have normals
{
Vector3[] normals = new Vector3[(int)vertCount];
ReadVector3ArrayBytes(normals, buf);
mesh.normals = normals;
}
if (((int)format & 4) != 0) // have tangents
{
Vector4[] tangents = new Vector4[(int)vertCount];
ReadVector4ArrayBytes(tangents, buf);
mesh.tangents = tangents;
}
if (((int)format & 8) != 0) // have UVs
{
Vector2[] uvs = new Vector2[(int)vertCount];
ReadVector2Array16bit(uvs, buf);
mesh.uv = uvs;
}
// triangle indices
int[] tris = new int[(int)triCount * 3];
for (i = 0; i < (int)triCount; i++)
{
tris[i * 3 + 0] = buf.ReadUInt16();
tris[i * 3 + 1] = buf.ReadUInt16();
tris[i * 3 + 2] = buf.ReadUInt16();
}
mesh.triangles = tris;
buf.Close();
return mesh;
}
public static void ReadVector3Array16bit(Vector3[] arr, BinaryReader buf)
{
int n = arr.Length;
if (n == 0) return;
// Read bounding box
Vector3 bmin;
Vector3 bmax;
bmin.x = buf.ReadSingle();
bmax.x = buf.ReadSingle();
bmin.y = buf.ReadSingle();
bmax.y = buf.ReadSingle();
bmin.z = buf.ReadSingle();
bmax.z = buf.ReadSingle();
// Decode vectors as 16 bit integer components between the bounds
for (int i = 0; i < n; ++i)
{
UInt16 ix = buf.ReadUInt16();
UInt16 iy = buf.ReadUInt16();
UInt16 iz = buf.ReadUInt16();
float xx = ix / 65535.0f * (bmax.x - bmin.x) + bmin.x;
float yy = iy / 65535.0f * (bmax.y - bmin.y) + bmin.y;
float zz = iz / 65535.0f * (bmax.z - bmin.z) + bmin.z;
arr[i] = new Vector3(xx, yy, zz);
}
}
public static void WriteVector3Array16bit(Vector3[] arr, BinaryWriter buf)
{
if (arr.Length == 0) return;
// Calculate bounding box of the array
Bounds bounds = new Bounds(arr[0], new Vector3(0.001f, 0.001f, 0.001f));
foreach (Vector3 v in arr)
{
bounds.Encapsulate(v);
}
// Write bounds to stream
Vector3 bmin = bounds.min;
Vector3 bmax = bounds.max;
buf.Write(bmin.x);
buf.Write(bmax.x);
buf.Write(bmin.y);
buf.Write(bmax.y);
buf.Write(bmin.z);
buf.Write(bmax.z);
// Encode vectors as 16 bit integer components between the bounds
foreach (Vector3 v in arr)
{
int xx = (int)Mathf.Clamp(
(v.x - bmin.x) / (bmax.x - bmin.x) * 65535.0f,
0.0f,
65535.0f);
int yy = (int)Mathf.Clamp(
(v.y - bmin.y) / (bmax.y - bmin.y) * 65535.0f,
0.0f,
65535.0f);
int zz = (int)Mathf.Clamp(
(v.z - bmin.z) / (bmax.z - bmin.z) * 65535.0f,
0.0f,
65535.0f);
UInt16 ix = (UInt16)xx;
UInt16 iy = (UInt16)yy;
UInt16 iz = (UInt16)zz;
buf.Write(ix);
buf.Write(iy);
buf.Write(iz);
}
}
public static void ReadVector2Array16bit(Vector2[] arr, BinaryReader buf)
{
int n = arr.Length;
if (n == 0) return;
// Read bounding box
Vector2 bmin;
Vector2 bmax;
bmin.x = buf.ReadSingle();
bmax.x = buf.ReadSingle();
bmin.y = buf.ReadSingle();
bmax.y = buf.ReadSingle();
// Decode vectors as 16 bit integer components between the bounds
for (int i = 0; i < n; ++i)
{
UInt16 ix = buf.ReadUInt16();
UInt16 iy = buf.ReadUInt16();
float xx = ix / 65535.0f * (bmax.x - bmin.x) + bmin.x;
float yy = iy / 65535.0f * (bmax.y - bmin.y) + bmin.y;
arr[i] = new Vector2(xx, yy);
}
}
public static void WriteVector2Array16bit(Vector2[] arr, BinaryWriter buf)
{
if (arr.Length == 0) return;
// Calculate bounding box of the array
Bounds bounds = new Bounds(arr[0], new Vector2(0.001f, 0.001f));
foreach (Vector2 v in arr)
{
bounds.Encapsulate(v);
}
// Write bounds to stream
Vector2 bmin = bounds.min;
Vector2 bmax = bounds.max;
buf.Write(bmin.x);
buf.Write(bmax.x);
buf.Write(bmin.y);
buf.Write(bmax.y);
// Encode vectors as 16 bit integer components between the bounds
foreach (Vector3 v in arr)
{
int xx = (int)Mathf.Clamp(
(v.x - bmin.x) / (bmax.x - bmin.x) * 65535.0f,
0.0f,
65535.0f);
int yy = (int)Mathf.Clamp(
(v.y - bmin.y) / (bmax.y - bmin.y) * 65535.0f,
0.0f,
65535.0f);
UInt16 ix = (UInt16)xx;
UInt16 iy = (UInt16)yy;
buf.Write(ix);
buf.Write(iy);
}
}
public static void ReadVector3ArrayBytes(Vector3[] arr, BinaryReader buf)
{
//Decode vectors as 8 bit integer components in -1.0 .. 1.0 range
int n = arr.Length;
for (int i = 0; i < n; ++i)
{
byte ix = buf.ReadByte();
byte iy = buf.ReadByte();
byte iz = buf.ReadByte();
float xx = ((float)(int)ix - 128f) / 127f;
float yy = ((float)(int)iy - 128f) / 127f;
float zz = ((float)(int)iz - 128f) / 127f;
arr[i] = new Vector3(xx, yy, zz);
}
}
public static void WriteVector3ArrayBytes(Vector3[] arr, BinaryWriter buf)
{
// Encode vectors as 8 bit integer components in -1.0 .. 1.0 range
foreach(Vector3 v in arr)
{
byte ix = (byte)Mathf.Clamp(v.x * 127f + 128f, 0f, 255f);
byte iy = (byte)Mathf.Clamp(v.y * 127f + 128f, 0f, 255f);
byte iz = (byte)Mathf.Clamp(v.z * 127f + 128f, 0f, 255f);
buf.Write(ix);
buf.Write(iy);
buf.Write(iz);
}
}
public static void ReadVector4ArrayBytes(Vector4[] arr, BinaryReader buf)
{
//Decode vectors as 8 bit integer components in -1.0 .. 1.0 range
int n = arr.Length;
for (int i = 0; i < n; ++i)
{
byte ix = buf.ReadByte();
byte iy = buf.ReadByte();
byte iz = buf.ReadByte();
byte iw = buf.ReadByte();
float xx = ((float)(int)ix - 128f) / 127f;
float yy = ((float)(int)iy - 128f) / 127f;
float zz = ((float)(int)iz - 128f) / 127f;
float ww = ((float)(int)iw - 128f) / 127f;
arr[i] = new Vector4(xx, yy, zz, ww);
}
}
public static void WriteVector4ArrayBytes(Vector4[] arr, BinaryWriter buf)
{
// Encode vectors as 8 bit integer components in -1.0 .. 1.0 range
foreach (Vector4 v in arr)
{
byte ix = (byte)Mathf.Clamp(v.x * 127f + 128f, 0f, 255f);
byte iy = (byte)Mathf.Clamp(v.y * 127f + 128f, 0f, 255f);
byte iz = (byte)Mathf.Clamp(v.z * 127f + 128f, 0f, 255f);
byte iw = (byte)Mathf.Clamp(v.w * 127f + 128f, 0f, 255f);
buf.Write(ix);
buf.Write(iy);
buf.Write(iz);
buf.Write(iw);
}
}
// Writes mesh to an array of bytes.
public static byte[] WriteMesh(Mesh mesh, bool saveTangents)
{
if (!mesh)
{
print("No mesh given!");
return null;
}
Vector3[] verts = mesh.vertices;
Vector3[] normals = mesh.normals;
Vector4[] tangents = mesh.tangents;
Vector2[] uvs = mesh.uv;
int[] tris = mesh.triangles;
// figure out vertex format
byte format = (byte)1;
if (normals.Length > 0)
{
format |= 2;
}
if (saveTangents && tangents.Length > 0)
{
format |= 4;
}
if (uvs.Length > 0)
{
format |= 8;
}
MemoryStream stream = new MemoryStream();
BinaryWriter buf = new BinaryWriter(stream);
// write header
UInt16 vertCount = (UInt16)verts.Length;
UInt16 triCount = (UInt16)(tris.Length / 3);
buf.Write(vertCount);
buf.Write(triCount);
buf.Write(format);
// vertex components
WriteVector3Array16bit(verts, buf);
WriteVector3ArrayBytes(normals, buf);
if (saveTangents)
{
WriteVector4ArrayBytes(tangents, buf);
}
WriteVector2Array16bit(uvs, buf);
// triangle indices
foreach (int idx in tris)
{
UInt16 idx16 = (UInt16)idx;
buf.Write(idx16);
}
buf.Close();
return stream.ToArray();
}
// Writes mesh to a local file, for loading with WWW interface later.
public static void WriteMeshToFileForWeb(Mesh mesh, string name, bool saveTangents)
{
// Write mesh to regular bytes
byte[] bytes = WriteMesh(mesh, saveTangents);
// Write to file
FileStream fs = new FileStream(name, FileMode.Create);
fs.Write(bytes, 0, bytes.Length);
fs.Close();
}
// Reads mesh from the given WWW (that is finished downloading already)
public static Mesh ReadMeshFromWWW(WWW download)
{
if (download.error != null)
{
print("Error downloading mesh: " + download.error);
return null;
}
if (!download.isDone)
{
print("Download must be finished already");
return null;
}
byte[] bytes = download.bytes;
return ReadMesh(bytes);
}
}