using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Globalization; using UnityEngine; namespace UnityEditor.Experimental.VFX.Utility { class PCache { public List properties; public List> buckets; public int elementCount; public enum Format { Ascii, Binary } public PCache() { properties = new List(); buckets = new List>(); elementCount = 0; } public void Clear() { properties.Clear(); buckets.Clear(); elementCount = 0; } public void AddFloatProperty(string name) { properties.Add(new PropertyDesc() { Name = name, ComponentIndex = 0, ComponentName = name, Type = "float" }); buckets.Add(new List()); } public void AddVector2Property(string name) { properties.Add(new PropertyDesc() { Name = name + ".x", ComponentIndex = 0, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".y", ComponentIndex = 1, ComponentName = name, Type = "float" }); buckets.Add(new List()); buckets.Add(new List()); } public void AddVector3Property(string name) { properties.Add(new PropertyDesc() { Name = name + ".x", ComponentIndex = 0, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".y", ComponentIndex = 1, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".z", ComponentIndex = 2, ComponentName = name, Type = "float" }); buckets.Add(new List()); buckets.Add(new List()); buckets.Add(new List()); } public void AddVector4Property(string name) { properties.Add(new PropertyDesc() { Name = name + ".x", ComponentIndex = 0, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".y", ComponentIndex = 1, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".z", ComponentIndex = 2, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".w", ComponentIndex = 3, ComponentName = name, Type = "float" }); buckets.Add(new List()); buckets.Add(new List()); buckets.Add(new List()); buckets.Add(new List()); } public void AddColorProperty(string name) { properties.Add(new PropertyDesc() { Name = name + ".r", ComponentIndex = 0, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".g", ComponentIndex = 1, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".b", ComponentIndex = 2, ComponentName = name, Type = "float" }); properties.Add(new PropertyDesc() { Name = name + ".a", ComponentIndex = 3, ComponentName = name, Type = "float" }); buckets.Add(new List()); buckets.Add(new List()); buckets.Add(new List()); buckets.Add(new List()); } public void SetFloatData(string property, List data) { var prop = properties.FirstOrDefault(o => o.Name == property); if (prop.Name != property) throw new InvalidOperationException("Could not find property :" + property); if (elementCount == 0 || data.Count == elementCount) { int index = properties.IndexOf(prop); foreach (float f in data) { buckets[index].Add(f); } if (elementCount == 0) elementCount = data.Count; } else throw new InvalidOperationException("Need to set data corresponding to the actual element count :" + elementCount); } public void SetVector3Data(string component, List dataX, List dataY, List dataZ) { SetFloatData(component + ".x", dataX); SetFloatData(component + ".y", dataY); SetFloatData(component + ".z", dataZ); } public void SetVector2Data(string component, List data) { var dataX = new List(); var dataY = new List(); foreach (var v in data) { dataX.Add(v.x); dataY.Add(v.y); } SetFloatData(component + ".x", dataX); SetFloatData(component + ".y", dataY); } public void SetVector3Data(string component, List data) { var dataX = new List(); var dataY = new List(); var dataZ = new List(); foreach (var v in data) { dataX.Add(v.x); dataY.Add(v.y); dataZ.Add(v.z); } SetFloatData(component + ".x", dataX); SetFloatData(component + ".y", dataY); SetFloatData(component + ".z", dataZ); } public void SetColorData(string component, List data) { var dataX = new List(); var dataY = new List(); var dataZ = new List(); var dataW = new List(); foreach (var v in data) { dataX.Add(v.x); dataY.Add(v.y); dataZ.Add(v.z); dataW.Add(v.w); } SetFloatData(component + ".r", dataX); SetFloatData(component + ".g", dataY); SetFloatData(component + ".b", dataZ); SetFloatData(component + ".a", dataW); } public void SetVector4Data(string component, List data) { var dataX = new List(); var dataY = new List(); var dataZ = new List(); var dataW = new List(); foreach (var v in data) { dataX.Add(v.x); dataY.Add(v.y); dataZ.Add(v.z); dataW.Add(v.w); } SetFloatData(component + ".x", dataX); SetFloatData(component + ".y", dataY); SetFloatData(component + ".z", dataZ); SetFloatData(component + ".w", dataW); } public void SaveToFile(string filePath, Format format = Format.Binary) { if (string.IsNullOrEmpty(filePath)) throw new InvalidOperationException("Cannot SaveToFile with an empty filepath"); var directory = Path.GetDirectoryName(filePath); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); if (File.Exists(filePath)) File.Delete(filePath); using (var binaryWriter = new BinaryWriter(File.Create(filePath))) { binaryWriter.Write(BuildHeaderString(format)); if (format == Format.Binary) { for (int i = 0; i < elementCount; i++) { for (int j = 0; j < properties.Count; j++) { var prop = properties[j]; switch (prop.Type) { case "char": binaryWriter.Write((sbyte)buckets[j][i]); break; case "uchar": binaryWriter.Write((byte)buckets[j][i]); break; case "short": binaryWriter.Write((short)buckets[j][i]); break; case "ushort": binaryWriter.Write((ushort)buckets[j][i]); break; case "int": binaryWriter.Write((int)buckets[j][i]); break; case "uint": binaryWriter.Write((uint)buckets[j][i]); break; case "float": binaryWriter.Write((float)buckets[j][i]); break; case "double": binaryWriter.Write((double)buckets[j][i]); break; } } } } else if (format == Format.Ascii) { var sb = new StringBuilder(); for (int i = 0; i < elementCount; i++) { for (int j = 0; j < properties.Count; j++) { var prop = properties[j]; switch (prop.Type) { case "char": sb.Append(((sbyte)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; case "uchar": sb.Append(((byte)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; case "short": sb.Append(((short)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; case "ushort": sb.Append(((ushort)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; case "int": sb.Append(((int)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; case "uint": sb.Append(((uint)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; case "float": sb.Append(((float)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; case "double": sb.Append(((double)buckets[j][i]).ToString(CultureInfo.InvariantCulture)); break; } sb.Append(j == properties.Count - 1 ? "\n" : " "); } binaryWriter.Write(sb.ToString().ToCharArray()); sb.Clear(); } } else throw new InvalidOperationException("Invalid format : " + format); } AssetDatabase.ImportAsset(filePath, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport); } private char[] BuildHeaderString(Format format) { StringBuilder b = new StringBuilder(); b.AppendLine("pcache"); b.AppendLine(string.Format("format {0} 1.0", GetFormatString(format))); b.AppendLine("comment Exported with PCache.cs"); b.AppendLine(string.Format("elements {0}", elementCount)); foreach (var property in properties) { b.AppendLine(string.Format("property {0} {1}", property.Type, property.Name)); } b.AppendLine("end_header"); return b.ToString().ToCharArray(); } public static PCache FromFile(string filename) { PCache data = new PCache(); List header; long offset; using (var stream = File.OpenRead(filename)) { GetHeader(stream, out offset, out header); } if (header == null || header[0] != "pcache") throw new Exception("Invalid header : missing magic number"); Format format = (Format)int.MaxValue; data.elementCount = 0; data.properties = new List(); foreach (string line in header) { var words = line.Split(' '); switch (words[0].ToLower()) { case "comment": //do nothing break; case "format": if (words.Length != 3) throw new Exception("Invalid format description :" + line); switch (words[1]) { case "ascii": format = Format.Ascii; break; case "binary": format = Format.Binary; break; default: throw new Exception("Invalid Format :" + words[1]); } break; case "elements": if (words.Length != 2) throw new Exception("Invalid element description :" + line); if (!int.TryParse(words[1], out data.elementCount)) throw new Exception("Invalid element count :" + words[1]); break; case "property": if (words.Length != 3) throw new Exception("Invalid property description :" + line); string property = words[2]; string component = GetComponentName(property); int idx = GetComponentIndex(property); string type = words[1]; int stride = GetPropertySize(type); if (stride == 0) throw new Exception("Invalid Type for " + property + " property : " + type); PropertyDesc prop = new PropertyDesc() { Name = property, Type = type, ComponentName = component, ComponentIndex = idx, Stride = stride }; data.properties.Add(prop); break; case "end_header": if (words.Length != 1) throw new Exception("Invalid end_header description :" + line); break; } } data.buckets = new List>(); foreach (var property in data.properties) { data.buckets.Add(new List(data.elementCount)); } if (format == Format.Binary) { using (var binaryReader = new BinaryReader(File.OpenRead(filename))) { binaryReader.BaseStream.Seek(offset, SeekOrigin.Begin); for (int i = 0; i < data.elementCount; i++) { for (int j = 0; j < data.properties.Count; j++) { var prop = data.properties[j]; switch (prop.Type) { case "short": data.buckets[j].Add(binaryReader.ReadInt16()); break; case "ushort": data.buckets[j].Add(binaryReader.ReadUInt16()); break; case "int": data.buckets[j].Add(binaryReader.ReadInt32()); break; case "uint": data.buckets[j].Add(binaryReader.ReadUInt32()); break; case "char": data.buckets[j].Add(binaryReader.ReadSByte()); break; case "uchar": data.buckets[j].Add(binaryReader.ReadByte()); break; case "float": data.buckets[j].Add(binaryReader.ReadSingle()); break; case "double": data.buckets[j].Add(binaryReader.ReadDouble()); break; } } } } } else if (format == Format.Ascii) { string[] lines = null; using (var reader = new StreamReader(File.OpenRead(filename))) { reader.BaseStream.Seek(offset, SeekOrigin.Begin); lines = reader.ReadToEnd().Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); } if (lines.Length != data.elementCount) throw new InvalidOperationException(string.Format("Bad item amount, {0} expected in header, found {1}", data.elementCount, lines.Length)); for (int i = 0; i < data.elementCount; i++) { string line = lines[i]; string[] elements = line.Split(' '); for (int j = 0; j < data.properties.Count; j++) { var prop = data.properties[j]; switch (prop.Type) { case "short": data.buckets[j].Add(short.Parse(elements[j], CultureInfo.InvariantCulture)); break; case "ushort": data.buckets[j].Add(ushort.Parse(elements[j], CultureInfo.InvariantCulture)); break; case "int": data.buckets[j].Add(int.Parse(elements[j], CultureInfo.InvariantCulture)); break; case "uint": data.buckets[j].Add(uint.Parse(elements[j], CultureInfo.InvariantCulture)); break; case "char": data.buckets[j].Add(sbyte.Parse(elements[j], CultureInfo.InvariantCulture)); break; case "uchar": data.buckets[j].Add(byte.Parse(elements[j], CultureInfo.InvariantCulture)); break; case "float": data.buckets[j].Add(float.Parse(elements[j], CultureInfo.InvariantCulture)); break; case "double": data.buckets[j].Add(double.Parse(elements[j], CultureInfo.InvariantCulture)); break; } } } } return data; } private static void GetHeader(Stream s, out long byteLength, out List lines) { byteLength = 0; bool found_end_header = false; lines = new List(); s.Seek(0, SeekOrigin.Begin); using (var sr = new BinaryReader(s)) { var sb = new StringBuilder(); do { bool newline = false; do { char c = sr.ReadChar(); byteLength++; if (c == '\r') { // skip } else if (c == '\n') { if (sb.Length > 0) newline = true; } else sb.Append(c); } while (!newline); string line = sb.ToString(); sb.Clear(); lines.Add(line); if (line == "end_header") found_end_header = true; } while (!found_end_header); } } private static string GetComponentName(string property) { string p = property.ToLower(); if (p.EndsWith(".x") || p.EndsWith(".y") || p.EndsWith(".z") || p.EndsWith(".w") || p.EndsWith(".r") || p.EndsWith(".g") || p.EndsWith(".b") || p.EndsWith(".a")) return property.Substring(0, property.Length - 2); else return property; } private static int GetComponentIndex(string property) { string p = property.ToLower(); if (p.EndsWith(".x") || p.EndsWith(".r")) return 0; else if (p.EndsWith(".y") || p.EndsWith(".g")) return 1; else if (p.EndsWith(".z") || p.EndsWith(".b")) return 2; else if (p.EndsWith(".w") || p.EndsWith(".a")) return 3; else return 0; } private static string GetFormatString(Format format) { switch (format) { case Format.Ascii: return "ascii"; case Format.Binary: return "binary"; default: throw new InvalidOperationException("Invalid format"); } } private static int GetPropertySize(string type) { if (TypeSize.ContainsKey(type)) return TypeSize[type]; else return 0; } private static Dictionary TypeSize = new Dictionary() { { "char", 1 }, { "uchar", 1 }, { "short", 2 }, { "ushort", 2 }, { "int", 4 }, { "uint", 4 }, { "float", 4 }, { "double", 8 }, }; public struct PropertyDesc { public string Name; public string Type; public string ComponentName; public int ComponentIndex; public int Stride; } } }