In order to improve my c# (which is fairly basic at best) I decided to try to serialize a simple mesh to a file using the JSON.net library.
Simple mesh in this case means an array of vertices and a number of arrays of triangles constructed from 3 vertices each. No materials or animations or anything fancy.
Vertices have a position, a normal, a tangent, 4 sets of texture coordinates and a color. Everything but the position is optional though.
As a starting point, I use the mesh class of the Unity3d. I convert this to my own mesh class and pass this into the JSON serializer. Deserializing works the same, just in the opposite direction.
I'm not too fussed about the Unity specific parts for now, I've put it in to make testing more convenient. I am planning to use JSON with a game project in Unity eventually but the program posted here is so far purely an educational thing.
using UnityEngine;
using Newtonsoft.Json;
using System;
using System.IO;
//naively represented geometry - nrg :)
[JsonConverter(typeof(VertexConverter))]
public class nrgVertex
{
private float[] _d;
public nrgVertex() { _d = new float[22]; }
public nrgVertex (float[] data) : this()
{
for (int i = 0; i < data.Length && i < _d.Length; ++i) _d[i] = data[i];
}
//expose data as array
public float[] data { get { return (float[])_d.Clone(); } }
//expose data as array without trailing zeros
public float[] shortData {
get
{
var temp = (float[])_d.Clone();
Array.Resize(ref temp, 1 + Array.FindLastIndex(temp, v => ((v < -0.000001) || (v > 0.000001))));
return temp;
}
}
//expose data component wise
//position xyz
public float x { get { return _d[0]; } }
public float y { get { return _d[1]; } }
public float z { get { return _d[2]; } }
//normal xyz
public float nx { get { return _d[3]; } }
public float ny { get { return _d[4]; } }
public float nz { get { return _d[5]; } }
//tangent xyz
public float tx { get { return _d[6]; } }
public float ty { get { return _d[7]; } }
public float tz { get { return _d[8]; } }
public float tw { get { return _d[9]; } }
//uv1
public float u { get { return _d[10]; } }
public float v { get { return _d[11]; } }
//uv2
public float u2 { get { return _d[12]; } }
public float v2 { get { return _d[13]; } }
//uv3
public float u3 { get { return _d[14]; } }
public float v3 { get { return _d[15]; } }
//uv4
public float u4 { get { return _d[16]; } }
public float v4 { get { return _d[17]; } }
//vertex color rgba
public float cr { get { return _d[18]; } }
public float cg { get { return _d[19]; } }
public float cb { get { return _d[20]; } }
public float ca { get { return _d[21]; } }
}
class VertexConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(nrgVertex);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var d = new float[22];
int i = 0;
if (reader.TokenType == JsonToken.StartArray) {
while (reader.Read() && (reader.TokenType != JsonToken.EndArray) && (i < d.Length))
{
d[i] = (float)(double)reader.Value;
++i;
}
}
return new nrgVertex(d);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var v = (nrgVertex)value;
writer.WriteRawValue(JsonConvert.SerializeObject(v.shortData, Formatting.None));
}
}
[JsonConverter(typeof(FaceConverter))]
public class nrgFace
{
private int[] _d;
public nrgFace() { _d = new int[3]; }
public nrgFace(int[] data) : this()
{
for (int i = 0; i < data.Length && i < _d.Length; ++i) _d[i] = data[i];
}
public int[] data { get { return (int[])_d.Clone(); } }
public int a { get { return _d[0]; } }
public int b { get { return _d[1]; } }
public int c { get { return _d[2]; } }
}
class FaceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(nrgFace);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var t = new int[3];
int i = 0;
if (reader.TokenType == JsonToken.StartArray) {
while (reader.Read() && (reader.TokenType != JsonToken.EndArray) && (i < t.Length)) {
t[i] = (int)(Int64)reader.Value;
++i;
}
}
return new nrgFace(t);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var t = (nrgFace)value;
writer.WriteRawValue(JsonConvert.SerializeObject(t.data, Formatting.None));
}
}
public class nrgMesh
{
public nrgVertex[] Vertices;
public nrgFace[][] Faces;
static public nrgMesh ConvertUnityToNrgMesh(Mesh source)
{
nrgMesh nrg = new nrgMesh();
int vertexCount = source.vertexCount;
nrg.Vertices = new nrgVertex[vertexCount];
for (int i = 0; i < vertexCount; ++i) {
var d = new float[22] {
(source.vertices != null && i < source.vertices.Length) ? source.vertices[i].x : 0.0f,
(source.vertices != null && i < source.vertices.Length) ? source.vertices[i].y : 0.0f,
(source.vertices != null && i < source.vertices.Length) ? source.vertices[i].z : 0.0f,
(source.normals != null && i < source.normals.Length) ? source.normals[i].x : 0.0f,
(source.normals != null && i < source.normals.Length) ? source.normals[i].y : 0.0f,
(source.normals != null && i < source.normals.Length) ? source.normals[i].z : 0.0f,
(source.tangents != null && i < source.tangents.Length) ? source.tangents[i].x : 0.0f,
(source.tangents != null && i < source.tangents.Length) ? source.tangents[i].y : 0.0f,
(source.tangents != null && i < source.tangents.Length) ? source.tangents[i].z : 0.0f,
(source.tangents != null && i < source.tangents.Length) ? source.tangents[i].w : 0.0f,
(source.uv != null && i < source.uv.Length) ? source.uv[i].x : 0.0f,
(source.uv != null && i < source.uv.Length) ? source.uv[i].y : 0.0f,
(source.uv2 != null && i < source.uv2.Length) ? source.uv2[i].x : 0.0f,
(source.uv2 != null && i < source.uv2.Length) ? source.uv2[i].y : 0.0f,
(source.uv3 != null && i < source.uv3.Length) ? source.uv3[i].x : 0.0f,
(source.uv3 != null && i < source.uv3.Length) ? source.uv3[i].y : 0.0f,
(source.uv4 != null && i < source.uv4.Length) ? source.uv4[i].x : 0.0f,
(source.uv4 != null && i < source.uv4.Length) ? source.uv4[i].y : 0.0f,
(source.colors != null && i < source.colors.Length) ? source.colors[i].r : 0.0f,
(source.colors != null && i < source.colors.Length) ? source.colors[i].g : 0.0f,
(source.colors != null && i < source.colors.Length) ? source.colors[i].b : 0.0f,
(source.colors != null && i < source.colors.Length) ? source.colors[i].a : 0.0f
};
nrg.Vertices[i] = new nrgVertex(d);
}
int submeshCount = source.subMeshCount;
nrg.Faces = new nrgFace[submeshCount][];
for (int i = 0; i < submeshCount; ++i) {
int[] submesh = source.GetTriangles(i);
int submeshLength = submesh.Length / 3;
nrg.Faces[i] = new nrgFace[submeshLength];
for (int j = 0; j < submeshLength; ++j)
{
nrg.Faces[i][j] = new nrgFace( new int[3] { submesh[(j*3)+0], submesh[(j*3)+1], submesh[(j*3)+2] } );
}
}
return nrg;
}
static public Mesh ConvertNrgToUnityMesh(nrgMesh source)
{
Mesh mesh = new Mesh();
int vertexCount = source.Vertices.Length;
Vector3[] pos = new Vector3[vertexCount];
Vector3[] nrm = new Vector3[vertexCount];
Vector4[] tan = new Vector4[vertexCount];
Vector2[] uv1 = new Vector2[vertexCount];
Vector2[] uv2 = new Vector2[vertexCount];
Vector2[] uv3 = new Vector2[vertexCount];
Vector2[] uv4 = new Vector2[vertexCount];
Color[] vcl = new Color[vertexCount];
for (int i = 0; i < vertexCount; ++i) {
nrgVertex v = source.Vertices[i];
pos[i] = new Vector3(v.x, v.y, v.z);
nrm[i] = new Vector3(v.nx, v.ny, v.nz);
tan[i] = new Vector4(v.tx, v.ty, v.tz, v.tw);
uv1[i] = new Vector2(v.u, v.v);
uv2[i] = new Vector2(v.u2, v.v2);
uv3[i] = new Vector2(v.u3, v.v3);
uv4[i] = new Vector2(v.u4, v.v4);
vcl[i] = new Color(v.cr, v.cg, v.cb, v.ca);
}
mesh.vertices = pos;
mesh.normals = nrm;
mesh.tangents = tan;
mesh.uv = uv1;
mesh.uv2 = uv2;
mesh.uv3 = uv3;
mesh.uv4 = uv4;
mesh.colors = vcl;
int submeshCount = source.Faces.Length;
for (int i = 0; i < submeshCount; ++i) {
int submeshLength = source.Faces[i].Length;
int[] faces = new int[submeshLength*3];
for (int j = 0; j < submeshLength; ++j) {
var f = source.Faces[i][j].data;
for (int k = 0; k < 3; ++k) faces[(j*3)+k] = f[k];
}
mesh.SetTriangles(faces, i);
}
return mesh;
}
public static void WriteToFile(string filename, nrgMesh mesh)
{
using (StreamWriter file = File.CreateText(filename))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Formatting = Formatting.Indented;
serializer.Serialize(file, mesh);
}
}
public static nrgMesh ReadFromFile(string filename)
{
using(StreamReader file = File.OpenText(filename))
{
JsonSerializer serializer = new JsonSerializer();
return (nrgMesh)serializer.Deserialize(file, typeof(nrgMesh));
}
}
}
public class JSON_test : MonoBehaviour {
public GameObject box;
private Mesh testmesh;
// Use this for initialization
void Start () {
testmesh = box.GetComponent<MeshFilter>().mesh;
if (testmesh == null) {
Debug.Log("no mesh.");
return;
}
nrgMesh mymesh = nrgMesh.ConvertUnityToNrgMesh(testmesh);
string filename = Application.dataPath + "/mesh.nrg";
nrgMesh.WriteToFile(filename, mymesh);
var mysecondmesh = nrgMesh.ReadFromFile(filename);
Mesh secondtestmesh = nrgMesh.ConvertNrgToUnityMesh(mysecondmesh);
box.GetComponent<MeshFilter>().mesh = secondtestmesh;
filename = Application.dataPath + "/mesh2.nrg";
nrgMesh.WriteToFile(filename, mysecondmesh);
}
}
This produces files like this:
{
"Vertices": [
[0.5,-0.5,0.5,0.0,0.0,1.0,-1.0,0.0,0.0,-1.0,0.0,0.0,0.6909865,0.3471644],
[-0.5,-0.5,0.5,0.0,0.0,1.0,-1.0,0.0,0.0,-1.0,1.0,0.0,0.9966581,0.3471644],
[0.5,0.5,0.5,0.0,0.0,1.0,-1.0,0.0,0.0,-1.0,0.0,1.0,0.6909865,0.652835846],
[-0.5,0.5,0.5,0.0,0.0,1.0,-1.0,0.0,0.0,-1.0,1.0,1.0,0.9966581,0.652835846],
[0.5,0.5,-0.5,0.0,1.0,0.0,-1.0,0.0,0.0,-1.0,0.0,1.0,0.3097273,0.347164243],
[-0.5,0.5,-0.5,0.0,1.0,0.0,-1.0,0.0,0.0,-1.0,1.0,1.0,0.00405576639,0.347164243],
[0.5,-0.5,-0.5,0.0,0.0,-1.0,-1.0,0.0,0.0,-1.0,0.0,1.0,0.347164243,0.652835846],
[-0.5,-0.5,-0.5,0.0,0.0,-1.0,-1.0,0.0,0.0,-1.0,1.0,1.0,0.652835846,0.652835846],
[0.5,0.5,0.5,0.0,1.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,0.3097273,0.652835846],
[-0.5,0.5,0.5,0.0,1.0,0.0,-1.0,0.0,0.0,-1.0,1.0,0.0,0.00405576639,0.652835846],
[0.5,0.5,-0.5,0.0,0.0,-1.0,-1.0,0.0,0.0,-1.0,0.0,0.0,0.347164243,0.347164243],
[-0.5,0.5,-0.5,0.0,0.0,-1.0,-1.0,0.0,0.0,-1.0,1.0,0.0,0.652835846,0.347164243],
[0.5,-0.5,-0.5,0.0,-1.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,0.347164243,0.00405576639],
[0.5,-0.5,0.5,0.0,-1.0,0.0,-1.0,0.0,0.0,-1.0,0.0,1.0,0.347164243,0.3097273],
[-0.5,-0.5,0.5,0.0,-1.0,0.0,-1.0,0.0,0.0,-1.0,1.0,1.0,0.652835846,0.3097273],
[-0.5,-0.5,-0.5,0.0,-1.0,0.0,-1.0,0.0,0.0,-1.0,1.0,0.0,0.652835846,0.00405576639],
[-0.5,-0.5,0.5,-1.0,0.0,0.0,0.0,0.0,-1.0,-1.0,0.0,0.0,0.6902728,0.3097273],
[-0.5,0.5,0.5,-1.0,0.0,0.0,0.0,0.0,-1.0,-1.0,0.0,1.0,0.995944262,0.3097273],
[-0.5,0.5,-0.5,-1.0,0.0,0.0,0.0,0.0,-1.0,-1.0,1.0,1.0,0.995944262,0.00405576639],
[-0.5,-0.5,-0.5,-1.0,0.0,0.0,0.0,0.0,-1.0,-1.0,1.0,0.0,0.6902728,0.00405576639],
[0.5,-0.5,-0.5,1.0,0.0,0.0,0.0,0.0,1.0,-1.0,0.0,0.0,0.3097273,0.00405576639],
[0.5,0.5,-0.5,1.0,0.0,0.0,0.0,0.0,1.0,-1.0,0.0,1.0,0.00405576639,0.00405576639],
[0.5,0.5,0.5,1.0,0.0,0.0,0.0,0.0,1.0,-1.0,1.0,1.0,0.00405576639,0.3097273],
[0.5,-0.5,0.5,1.0,0.0,0.0,0.0,0.0,1.0,-1.0,1.0,0.0,0.3097273,0.3097273]
],
"Faces": [
[
[0,2,3],
[0,3,1],
[8,4,5],
[8,5,9],
[10,6,7],
[10,7,11],
[12,13,14],
[12,14,15],
[16,17,18],
[16,18,19],
[20,21,22],
[20,22,23]
]
]
}
There are a few things I'm unsure about:
I'm not confident that the weird array construct in the
nrgVertex
andnrgFace
classes is a good idea. However I prefer the data to be in an array in the file as the JSON will otherwise include a label for each member of every vertex. Way to verbose I think.I have read about mutable and immutable structs and classes, I decided that vertexes and faces should be immutable in my mesh since it is intended to define the structure of the file only, not for editing the data. Are they though? Also, should I try to make
nrgMesh
itself immutable as well?Most of the code that interacts with JSON.net is directly from the docs or from other examples google turned up. Especially the implementation of the
JsonConverter
classes. They seem to work, but are they any good?I probably should do something with a namespace. I'm not sure if this would be appropriate in this case.
My goal is to get better at using c# and JSON.net, feedback would be very helpful.