123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- // Pcx - Point cloud importer & renderer for Unity
- // https://github.com/keijiro/Pcx
- using UnityEngine;
- using UnityEngine.Rendering;
- using UnityEditor;
- using UnityEditor.Experimental.AssetImporters;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- namespace Pcx
- {
- [ScriptedImporter(1, "ply")]
- class PlyImporter : ScriptedImporter
- {
- #region ScriptedImporter implementation
- public enum ContainerType { Mesh, ComputeBuffer, Texture }
- [SerializeField] ContainerType _containerType = ContainerType.Mesh;
- public override void OnImportAsset(AssetImportContext context)
- {
- if (_containerType == ContainerType.Mesh)
- {
- // Mesh container
- // Create a prefab with MeshFilter/MeshRenderer.
- var gameObject = new GameObject();
- var mesh = ImportAsMesh(context.assetPath);
- var meshFilter = gameObject.AddComponent<MeshFilter>();
- meshFilter.sharedMesh = mesh;
- var meshRenderer = gameObject.AddComponent<MeshRenderer>();
- meshRenderer.sharedMaterial = GetDefaultMaterial();
- context.AddObjectToAsset("prefab", gameObject);
- if (mesh != null) context.AddObjectToAsset("mesh", mesh);
- context.SetMainObject(gameObject);
- }
- else if (_containerType == ContainerType.ComputeBuffer)
- {
- // ComputeBuffer container
- // Create a prefab with PointCloudRenderer.
- var gameObject = new GameObject();
- var data = ImportAsPointCloudData(context.assetPath);
- var renderer = gameObject.AddComponent<PointCloudRenderer>();
- renderer.sourceData = data;
- context.AddObjectToAsset("prefab", gameObject);
- if (data != null) context.AddObjectToAsset("data", data);
- context.SetMainObject(gameObject);
- }
- else // _containerType == ContainerType.Texture
- {
- // Texture container
- // No prefab is available for this type.
- var data = ImportAsBakedPointCloud(context.assetPath);
- if (data != null)
- {
- context.AddObjectToAsset("container", data);
- context.AddObjectToAsset("position", data.positionMap);
- context.AddObjectToAsset("color", data.colorMap);
- context.SetMainObject(data);
- }
- }
- }
- #endregion
- #region Internal utilities
- static Material GetDefaultMaterial()
- {
- // Via package manager
- var path_upm = "Packages/jp.keijiro.pcx/Editor/Default Point.mat";
- // Via project asset database
- var path_prj = "Assets/Pcx/Editor/Default Point.mat";
- return AssetDatabase.LoadAssetAtPath<Material>(path_upm) ??
- AssetDatabase.LoadAssetAtPath<Material>(path_prj);
- }
- #endregion
- #region Internal data structure
- enum DataProperty {
- Invalid,
- R8, G8, B8, A8,
- R16, G16, B16, A16,
- SingleX, SingleY, SingleZ,
- DoubleX, DoubleY, DoubleZ,
- Data8, Data16, Data32, Data64
- }
- static int GetPropertySize(DataProperty p)
- {
- switch (p)
- {
- case DataProperty.R8: return 1;
- case DataProperty.G8: return 1;
- case DataProperty.B8: return 1;
- case DataProperty.A8: return 1;
- case DataProperty.R16: return 2;
- case DataProperty.G16: return 2;
- case DataProperty.B16: return 2;
- case DataProperty.A16: return 2;
- case DataProperty.SingleX: return 4;
- case DataProperty.SingleY: return 4;
- case DataProperty.SingleZ: return 4;
- case DataProperty.DoubleX: return 8;
- case DataProperty.DoubleY: return 8;
- case DataProperty.DoubleZ: return 8;
- case DataProperty.Data8: return 1;
- case DataProperty.Data16: return 2;
- case DataProperty.Data32: return 4;
- case DataProperty.Data64: return 8;
- }
- return 0;
- }
- class DataHeader
- {
- public List<DataProperty> properties = new List<DataProperty>();
- public int vertexCount = -1;
- }
- class DataBody
- {
- public List<Vector3> vertices;
- public List<Color32> colors;
- public DataBody(int vertexCount)
- {
- vertices = new List<Vector3>(vertexCount);
- colors = new List<Color32>(vertexCount);
- }
- public void AddPoint(
- float x, float y, float z,
- byte r, byte g, byte b, byte a
- )
- {
- vertices.Add(new Vector3(x, y, z));
- colors.Add(new Color32(r, g, b, a));
- }
- }
- #endregion
- #region Reader implementation
- Mesh ImportAsMesh(string path)
- {
- try
- {
- var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
- var header = ReadDataHeader(new StreamReader(stream));
- var body = ReadDataBody(header, new BinaryReader(stream));
- var mesh = new Mesh();
- mesh.name = Path.GetFileNameWithoutExtension(path);
- mesh.indexFormat = header.vertexCount > 65535 ?
- IndexFormat.UInt32 : IndexFormat.UInt16;
- mesh.SetVertices(body.vertices);
- mesh.SetColors(body.colors);
- mesh.SetIndices(
- Enumerable.Range(0, header.vertexCount).ToArray(),
- MeshTopology.Points, 0
- );
- mesh.UploadMeshData(true);
- return mesh;
- }
- catch (Exception e)
- {
- Debug.LogError("Failed importing " + path + ". " + e.Message);
- return null;
- }
- }
- PointCloudData ImportAsPointCloudData(string path)
- {
- try
- {
- var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
- var header = ReadDataHeader(new StreamReader(stream));
- var body = ReadDataBody(header, new BinaryReader(stream));
- var data = ScriptableObject.CreateInstance<PointCloudData>();
- data.Initialize(body.vertices, body.colors);
- data.name = Path.GetFileNameWithoutExtension(path);
- return data;
- }
- catch (Exception e)
- {
- Debug.LogError("Failed importing " + path + ". " + e.Message);
- return null;
- }
- }
- BakedPointCloud ImportAsBakedPointCloud(string path)
- {
- try
- {
- var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
- var header = ReadDataHeader(new StreamReader(stream));
- var body = ReadDataBody(header, new BinaryReader(stream));
- var data = ScriptableObject.CreateInstance<BakedPointCloud>();
- data.Initialize(body.vertices, body.colors);
- data.name = Path.GetFileNameWithoutExtension(path);
- return data;
- }
- catch (Exception e)
- {
- Debug.LogError("Failed importing " + path + ". " + e.Message);
- return null;
- }
- }
- DataHeader ReadDataHeader(StreamReader reader)
- {
- var data = new DataHeader();
- var readCount = 0;
- // Magic number line ("ply")
- var line = reader.ReadLine();
- readCount += line.Length + 1;
- if (line != "ply")
- throw new ArgumentException("Magic number ('ply') mismatch.");
- // Data format: check if it's binary/little endian.
- line = reader.ReadLine();
- readCount += line.Length + 1;
- if (line != "format binary_little_endian 1.0")
- throw new ArgumentException(
- "Invalid data format ('" + line + "'). " +
- "Should be binary/little endian.");
- // Read header contents.
- for (var skip = false;;)
- {
- // Read a line and split it with white space.
- line = reader.ReadLine();
- readCount += line.Length + 1;
- if (line == "end_header") break;
- var col = line.Split();
- // Element declaration (unskippable)
- if (col[0] == "element")
- {
- if (col[1] == "vertex")
- {
- data.vertexCount = Convert.ToInt32(col[2]);
- skip = false;
- }
- else
- {
- // Don't read elements other than vertices.
- skip = true;
- }
- }
- if (skip) continue;
- // Property declaration line
- if (col[0] == "property")
- {
- var prop = DataProperty.Invalid;
- // Parse the property name entry.
- switch (col[2])
- {
- case "red" : prop = DataProperty.R8; break;
- case "green": prop = DataProperty.G8; break;
- case "blue" : prop = DataProperty.B8; break;
- case "alpha": prop = DataProperty.A8; break;
- case "x" : prop = DataProperty.SingleX; break;
- case "y" : prop = DataProperty.SingleY; break;
- case "z" : prop = DataProperty.SingleZ; break;
- }
- // Check the property type.
- if (col[1] == "char" || col[1] == "uchar" ||
- col[1] == "int8" || col[1] == "uint8")
- {
- if (prop == DataProperty.Invalid)
- prop = DataProperty.Data8;
- else if (GetPropertySize(prop) != 1)
- throw new ArgumentException("Invalid property type ('" + line + "').");
- }
- else if (col[1] == "short" || col[1] == "ushort" ||
- col[1] == "int16" || col[1] == "uint16")
- {
- switch (prop)
- {
- case DataProperty.Invalid: prop = DataProperty.Data16; break;
- case DataProperty.R8: prop = DataProperty.R16; break;
- case DataProperty.G8: prop = DataProperty.G16; break;
- case DataProperty.B8: prop = DataProperty.B16; break;
- case DataProperty.A8: prop = DataProperty.A16; break;
- }
- if (GetPropertySize(prop) != 2)
- throw new ArgumentException("Invalid property type ('" + line + "').");
- }
- else if (col[1] == "int" || col[1] == "uint" || col[1] == "float" ||
- col[1] == "int32" || col[1] == "uint32" || col[1] == "float32")
- {
- if (prop == DataProperty.Invalid)
- prop = DataProperty.Data32;
- else if (GetPropertySize(prop) != 4)
- throw new ArgumentException("Invalid property type ('" + line + "').");
- }
- else if (col[1] == "int64" || col[1] == "uint64" ||
- col[1] == "double" || col[1] == "float64")
- {
- switch (prop)
- {
- case DataProperty.Invalid: prop = DataProperty.Data64; break;
- case DataProperty.SingleX: prop = DataProperty.DoubleX; break;
- case DataProperty.SingleY: prop = DataProperty.DoubleY; break;
- case DataProperty.SingleZ: prop = DataProperty.DoubleZ; break;
- }
- if (GetPropertySize(prop) != 8)
- throw new ArgumentException("Invalid property type ('" + line + "').");
- }
- else
- {
- throw new ArgumentException("Unsupported property type ('" + line + "').");
- }
- data.properties.Add(prop);
- }
- }
- // Rewind the stream back to the exact position of the reader.
- reader.BaseStream.Position = readCount;
- return data;
- }
- DataBody ReadDataBody(DataHeader header, BinaryReader reader)
- {
- var data = new DataBody(header.vertexCount);
- float x = 0, y = 0, z = 0;
- Byte r = 255, g = 255, b = 255, a = 255;
- for (var i = 0; i < header.vertexCount; i++)
- {
- foreach (var prop in header.properties)
- {
- switch (prop)
- {
- case DataProperty.R8: r = reader.ReadByte(); break;
- case DataProperty.G8: g = reader.ReadByte(); break;
- case DataProperty.B8: b = reader.ReadByte(); break;
- case DataProperty.A8: a = reader.ReadByte(); break;
- case DataProperty.R16: r = (byte)(reader.ReadUInt16() >> 8); break;
- case DataProperty.G16: g = (byte)(reader.ReadUInt16() >> 8); break;
- case DataProperty.B16: b = (byte)(reader.ReadUInt16() >> 8); break;
- case DataProperty.A16: a = (byte)(reader.ReadUInt16() >> 8); break;
- case DataProperty.SingleX: x = reader.ReadSingle(); break;
- case DataProperty.SingleY: y = reader.ReadSingle(); break;
- case DataProperty.SingleZ: z = reader.ReadSingle(); break;
- case DataProperty.DoubleX: x = (float)reader.ReadDouble(); break;
- case DataProperty.DoubleY: y = (float)reader.ReadDouble(); break;
- case DataProperty.DoubleZ: z = (float)reader.ReadDouble(); break;
- case DataProperty.Data8: reader.ReadByte(); break;
- case DataProperty.Data16: reader.BaseStream.Position += 2; break;
- case DataProperty.Data32: reader.BaseStream.Position += 4; break;
- case DataProperty.Data64: reader.BaseStream.Position += 8; break;
- }
- }
- data.AddPoint(x, y, z, r, g, b, a);
- }
- return data;
- }
- }
- #endregion
- }
|