PlyImporter.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // Pcx - Point cloud importer & renderer for Unity
  2. // https://github.com/keijiro/Pcx
  3. using UnityEngine;
  4. using UnityEngine.Rendering;
  5. using UnityEditor;
  6. using UnityEditor.Experimental.AssetImporters;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Linq;
  11. namespace Pcx
  12. {
  13. [ScriptedImporter(1, "ply")]
  14. class PlyImporter : ScriptedImporter
  15. {
  16. #region ScriptedImporter implementation
  17. public enum ContainerType { Mesh, ComputeBuffer, Texture }
  18. [SerializeField] ContainerType _containerType = ContainerType.Mesh;
  19. public override void OnImportAsset(AssetImportContext context)
  20. {
  21. if (_containerType == ContainerType.Mesh)
  22. {
  23. // Mesh container
  24. // Create a prefab with MeshFilter/MeshRenderer.
  25. var gameObject = new GameObject();
  26. var mesh = ImportAsMesh(context.assetPath);
  27. var meshFilter = gameObject.AddComponent<MeshFilter>();
  28. meshFilter.sharedMesh = mesh;
  29. var meshRenderer = gameObject.AddComponent<MeshRenderer>();
  30. meshRenderer.sharedMaterial = GetDefaultMaterial();
  31. context.AddObjectToAsset("prefab", gameObject);
  32. if (mesh != null) context.AddObjectToAsset("mesh", mesh);
  33. context.SetMainObject(gameObject);
  34. }
  35. else if (_containerType == ContainerType.ComputeBuffer)
  36. {
  37. // ComputeBuffer container
  38. // Create a prefab with PointCloudRenderer.
  39. var gameObject = new GameObject();
  40. var data = ImportAsPointCloudData(context.assetPath);
  41. var renderer = gameObject.AddComponent<PointCloudRenderer>();
  42. renderer.sourceData = data;
  43. context.AddObjectToAsset("prefab", gameObject);
  44. if (data != null) context.AddObjectToAsset("data", data);
  45. context.SetMainObject(gameObject);
  46. }
  47. else // _containerType == ContainerType.Texture
  48. {
  49. // Texture container
  50. // No prefab is available for this type.
  51. var data = ImportAsBakedPointCloud(context.assetPath);
  52. if (data != null)
  53. {
  54. context.AddObjectToAsset("container", data);
  55. context.AddObjectToAsset("position", data.positionMap);
  56. context.AddObjectToAsset("color", data.colorMap);
  57. context.SetMainObject(data);
  58. }
  59. }
  60. }
  61. #endregion
  62. #region Internal utilities
  63. static Material GetDefaultMaterial()
  64. {
  65. // Via package manager
  66. var path_upm = "Packages/jp.keijiro.pcx/Editor/Default Point.mat";
  67. // Via project asset database
  68. var path_prj = "Assets/Pcx/Editor/Default Point.mat";
  69. return AssetDatabase.LoadAssetAtPath<Material>(path_upm) ??
  70. AssetDatabase.LoadAssetAtPath<Material>(path_prj);
  71. }
  72. #endregion
  73. #region Internal data structure
  74. enum DataProperty {
  75. Invalid,
  76. R8, G8, B8, A8,
  77. R16, G16, B16, A16,
  78. SingleX, SingleY, SingleZ,
  79. DoubleX, DoubleY, DoubleZ,
  80. Data8, Data16, Data32, Data64
  81. }
  82. static int GetPropertySize(DataProperty p)
  83. {
  84. switch (p)
  85. {
  86. case DataProperty.R8: return 1;
  87. case DataProperty.G8: return 1;
  88. case DataProperty.B8: return 1;
  89. case DataProperty.A8: return 1;
  90. case DataProperty.R16: return 2;
  91. case DataProperty.G16: return 2;
  92. case DataProperty.B16: return 2;
  93. case DataProperty.A16: return 2;
  94. case DataProperty.SingleX: return 4;
  95. case DataProperty.SingleY: return 4;
  96. case DataProperty.SingleZ: return 4;
  97. case DataProperty.DoubleX: return 8;
  98. case DataProperty.DoubleY: return 8;
  99. case DataProperty.DoubleZ: return 8;
  100. case DataProperty.Data8: return 1;
  101. case DataProperty.Data16: return 2;
  102. case DataProperty.Data32: return 4;
  103. case DataProperty.Data64: return 8;
  104. }
  105. return 0;
  106. }
  107. class DataHeader
  108. {
  109. public List<DataProperty> properties = new List<DataProperty>();
  110. public int vertexCount = -1;
  111. }
  112. class DataBody
  113. {
  114. public List<Vector3> vertices;
  115. public List<Color32> colors;
  116. public DataBody(int vertexCount)
  117. {
  118. vertices = new List<Vector3>(vertexCount);
  119. colors = new List<Color32>(vertexCount);
  120. }
  121. public void AddPoint(
  122. float x, float y, float z,
  123. byte r, byte g, byte b, byte a
  124. )
  125. {
  126. vertices.Add(new Vector3(x, y, z));
  127. colors.Add(new Color32(r, g, b, a));
  128. }
  129. }
  130. #endregion
  131. #region Reader implementation
  132. Mesh ImportAsMesh(string path)
  133. {
  134. try
  135. {
  136. var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
  137. var header = ReadDataHeader(new StreamReader(stream));
  138. var body = ReadDataBody(header, new BinaryReader(stream));
  139. var mesh = new Mesh();
  140. mesh.name = Path.GetFileNameWithoutExtension(path);
  141. mesh.indexFormat = header.vertexCount > 65535 ?
  142. IndexFormat.UInt32 : IndexFormat.UInt16;
  143. mesh.SetVertices(body.vertices);
  144. mesh.SetColors(body.colors);
  145. mesh.SetIndices(
  146. Enumerable.Range(0, header.vertexCount).ToArray(),
  147. MeshTopology.Points, 0
  148. );
  149. mesh.UploadMeshData(true);
  150. return mesh;
  151. }
  152. catch (Exception e)
  153. {
  154. Debug.LogError("Failed importing " + path + ". " + e.Message);
  155. return null;
  156. }
  157. }
  158. PointCloudData ImportAsPointCloudData(string path)
  159. {
  160. try
  161. {
  162. var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
  163. var header = ReadDataHeader(new StreamReader(stream));
  164. var body = ReadDataBody(header, new BinaryReader(stream));
  165. var data = ScriptableObject.CreateInstance<PointCloudData>();
  166. data.Initialize(body.vertices, body.colors);
  167. data.name = Path.GetFileNameWithoutExtension(path);
  168. return data;
  169. }
  170. catch (Exception e)
  171. {
  172. Debug.LogError("Failed importing " + path + ". " + e.Message);
  173. return null;
  174. }
  175. }
  176. BakedPointCloud ImportAsBakedPointCloud(string path)
  177. {
  178. try
  179. {
  180. var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
  181. var header = ReadDataHeader(new StreamReader(stream));
  182. var body = ReadDataBody(header, new BinaryReader(stream));
  183. var data = ScriptableObject.CreateInstance<BakedPointCloud>();
  184. data.Initialize(body.vertices, body.colors);
  185. data.name = Path.GetFileNameWithoutExtension(path);
  186. return data;
  187. }
  188. catch (Exception e)
  189. {
  190. Debug.LogError("Failed importing " + path + ". " + e.Message);
  191. return null;
  192. }
  193. }
  194. DataHeader ReadDataHeader(StreamReader reader)
  195. {
  196. var data = new DataHeader();
  197. var readCount = 0;
  198. // Magic number line ("ply")
  199. var line = reader.ReadLine();
  200. readCount += line.Length + 1;
  201. if (line != "ply")
  202. throw new ArgumentException("Magic number ('ply') mismatch.");
  203. // Data format: check if it's binary/little endian.
  204. line = reader.ReadLine();
  205. readCount += line.Length + 1;
  206. if (line != "format binary_little_endian 1.0")
  207. throw new ArgumentException(
  208. "Invalid data format ('" + line + "'). " +
  209. "Should be binary/little endian.");
  210. // Read header contents.
  211. for (var skip = false;;)
  212. {
  213. // Read a line and split it with white space.
  214. line = reader.ReadLine();
  215. readCount += line.Length + 1;
  216. if (line == "end_header") break;
  217. var col = line.Split();
  218. // Element declaration (unskippable)
  219. if (col[0] == "element")
  220. {
  221. if (col[1] == "vertex")
  222. {
  223. data.vertexCount = Convert.ToInt32(col[2]);
  224. skip = false;
  225. }
  226. else
  227. {
  228. // Don't read elements other than vertices.
  229. skip = true;
  230. }
  231. }
  232. if (skip) continue;
  233. // Property declaration line
  234. if (col[0] == "property")
  235. {
  236. var prop = DataProperty.Invalid;
  237. // Parse the property name entry.
  238. switch (col[2])
  239. {
  240. case "red" : prop = DataProperty.R8; break;
  241. case "green": prop = DataProperty.G8; break;
  242. case "blue" : prop = DataProperty.B8; break;
  243. case "alpha": prop = DataProperty.A8; break;
  244. case "x" : prop = DataProperty.SingleX; break;
  245. case "y" : prop = DataProperty.SingleY; break;
  246. case "z" : prop = DataProperty.SingleZ; break;
  247. }
  248. // Check the property type.
  249. if (col[1] == "char" || col[1] == "uchar" ||
  250. col[1] == "int8" || col[1] == "uint8")
  251. {
  252. if (prop == DataProperty.Invalid)
  253. prop = DataProperty.Data8;
  254. else if (GetPropertySize(prop) != 1)
  255. throw new ArgumentException("Invalid property type ('" + line + "').");
  256. }
  257. else if (col[1] == "short" || col[1] == "ushort" ||
  258. col[1] == "int16" || col[1] == "uint16")
  259. {
  260. switch (prop)
  261. {
  262. case DataProperty.Invalid: prop = DataProperty.Data16; break;
  263. case DataProperty.R8: prop = DataProperty.R16; break;
  264. case DataProperty.G8: prop = DataProperty.G16; break;
  265. case DataProperty.B8: prop = DataProperty.B16; break;
  266. case DataProperty.A8: prop = DataProperty.A16; break;
  267. }
  268. if (GetPropertySize(prop) != 2)
  269. throw new ArgumentException("Invalid property type ('" + line + "').");
  270. }
  271. else if (col[1] == "int" || col[1] == "uint" || col[1] == "float" ||
  272. col[1] == "int32" || col[1] == "uint32" || col[1] == "float32")
  273. {
  274. if (prop == DataProperty.Invalid)
  275. prop = DataProperty.Data32;
  276. else if (GetPropertySize(prop) != 4)
  277. throw new ArgumentException("Invalid property type ('" + line + "').");
  278. }
  279. else if (col[1] == "int64" || col[1] == "uint64" ||
  280. col[1] == "double" || col[1] == "float64")
  281. {
  282. switch (prop)
  283. {
  284. case DataProperty.Invalid: prop = DataProperty.Data64; break;
  285. case DataProperty.SingleX: prop = DataProperty.DoubleX; break;
  286. case DataProperty.SingleY: prop = DataProperty.DoubleY; break;
  287. case DataProperty.SingleZ: prop = DataProperty.DoubleZ; break;
  288. }
  289. if (GetPropertySize(prop) != 8)
  290. throw new ArgumentException("Invalid property type ('" + line + "').");
  291. }
  292. else
  293. {
  294. throw new ArgumentException("Unsupported property type ('" + line + "').");
  295. }
  296. data.properties.Add(prop);
  297. }
  298. }
  299. // Rewind the stream back to the exact position of the reader.
  300. reader.BaseStream.Position = readCount;
  301. return data;
  302. }
  303. DataBody ReadDataBody(DataHeader header, BinaryReader reader)
  304. {
  305. var data = new DataBody(header.vertexCount);
  306. float x = 0, y = 0, z = 0;
  307. Byte r = 255, g = 255, b = 255, a = 255;
  308. for (var i = 0; i < header.vertexCount; i++)
  309. {
  310. foreach (var prop in header.properties)
  311. {
  312. switch (prop)
  313. {
  314. case DataProperty.R8: r = reader.ReadByte(); break;
  315. case DataProperty.G8: g = reader.ReadByte(); break;
  316. case DataProperty.B8: b = reader.ReadByte(); break;
  317. case DataProperty.A8: a = reader.ReadByte(); break;
  318. case DataProperty.R16: r = (byte)(reader.ReadUInt16() >> 8); break;
  319. case DataProperty.G16: g = (byte)(reader.ReadUInt16() >> 8); break;
  320. case DataProperty.B16: b = (byte)(reader.ReadUInt16() >> 8); break;
  321. case DataProperty.A16: a = (byte)(reader.ReadUInt16() >> 8); break;
  322. case DataProperty.SingleX: x = reader.ReadSingle(); break;
  323. case DataProperty.SingleY: y = reader.ReadSingle(); break;
  324. case DataProperty.SingleZ: z = reader.ReadSingle(); break;
  325. case DataProperty.DoubleX: x = (float)reader.ReadDouble(); break;
  326. case DataProperty.DoubleY: y = (float)reader.ReadDouble(); break;
  327. case DataProperty.DoubleZ: z = (float)reader.ReadDouble(); break;
  328. case DataProperty.Data8: reader.ReadByte(); break;
  329. case DataProperty.Data16: reader.BaseStream.Position += 2; break;
  330. case DataProperty.Data32: reader.BaseStream.Position += 4; break;
  331. case DataProperty.Data64: reader.BaseStream.Position += 8; break;
  332. }
  333. }
  334. data.AddPoint(x, y, z, r, g, b, a);
  335. }
  336. return data;
  337. }
  338. }
  339. #endregion
  340. }