NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/parser/ply-parser.js

  1. /**
  2. * @file Ply Parser
  3. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  4. * @private
  5. */
  6.  
  7. import {
  8. Geometry, Vector3, Face3, Color
  9. } from '../../lib/three.es6.js'
  10.  
  11. import { ParserRegistry } from '../globals.js'
  12. import SurfaceParser from './surface-parser.js'
  13.  
  14. /**
  15. * PLYLoader
  16. * @class
  17. * @private
  18. * @author Wei Meng / http://about.me/menway
  19. *
  20. * @description
  21. * A THREE loader for PLY ASCII files (known as the Polygon File Format or the Stanford Triangle Format).
  22. *
  23. * Limitations: ASCII decoding assumes file is UTF-8.
  24. *
  25. * @example
  26. * var loader = new THREE.PLYLoader();
  27. * loader.load('./models/ply/ascii/dolphins.ply', function (geometry) {
  28. * scene.add( new THREE.Mesh( geometry ) );
  29. * } );
  30. *
  31. * // If the PLY file uses non standard property names, they can be mapped while
  32. * // loading. For example, the following maps the properties
  33. * // “diffuse_(red|green|blue)” in the file to standard color names.
  34. *
  35. * loader.setPropertyNameMapping( {
  36. * diffuse_red: 'red',
  37. * diffuse_green: 'green',
  38. * diffuse_blue: 'blue'
  39. * } );
  40. *
  41. */
  42. function PLYLoader () {
  43. this.propertyNameMapping = {}
  44. }
  45.  
  46. PLYLoader.prototype = {
  47.  
  48. constructor: PLYLoader,
  49.  
  50. setPropertyNameMapping: function (mapping) {
  51. this.propertyNameMapping = mapping
  52. },
  53.  
  54. bin2str: function (buf) {
  55. var arrayBuffer = new Uint8Array(buf)
  56. var str = ''
  57. for (var i = 0; i < buf.byteLength; i++) {
  58. str += String.fromCharCode(arrayBuffer[ i ]) // implicitly assumes little-endian
  59. }
  60.  
  61. return str
  62. },
  63.  
  64. isASCII: function (data) {
  65. var header = this.parseHeader(this.bin2str(data))
  66.  
  67. return header.format === 'ascii'
  68. },
  69.  
  70. parse: function (data) {
  71. if (data instanceof ArrayBuffer) {
  72. return (
  73. this.isASCII(data)
  74. ? this.parseASCII(this.bin2str(data))
  75. : this.parseBinary(data)
  76. )
  77. } else {
  78. return this.parseASCII(data)
  79. }
  80. },
  81.  
  82. parseHeader: function (data) {
  83. var patternHeader = /ply([\s\S]*)end_header\s/
  84. var headerText = ''
  85. var headerLength = 0
  86. var result = patternHeader.exec(data)
  87. if (result !== null) {
  88. headerText = result[ 1 ]
  89. headerLength = result[ 0 ].length
  90. }
  91.  
  92. var header = {
  93. comments: [],
  94. elements: [],
  95. headerLength: headerLength
  96. }
  97.  
  98. var lines = headerText.split('\n')
  99. var currentElement, lineType, lineValues
  100.  
  101. function makePlyElementProperty (propertValues, propertyNameMapping) {
  102. var property = {
  103. type: propertValues[ 0 ]
  104. }
  105.  
  106. if (property.type === 'list') {
  107. property.name = propertValues[ 3 ]
  108. property.countType = propertValues[ 1 ]
  109. property.itemType = propertValues[ 2 ]
  110. } else {
  111. property.name = propertValues[ 1 ]
  112. }
  113.  
  114. if (property.name in propertyNameMapping) {
  115. property.name = propertyNameMapping[ property.name ]
  116. }
  117.  
  118. return property
  119. }
  120.  
  121. for (var i = 0; i < lines.length; i++) {
  122. var line = lines[ i ]
  123. line = line.trim()
  124. if (line === '') {
  125. continue
  126. }
  127. lineValues = line.split(/\s+/)
  128. lineType = lineValues.shift()
  129. line = lineValues.join(' ')
  130.  
  131. switch (lineType) {
  132. case 'format':
  133.  
  134. header.format = lineValues[ 0 ]
  135. header.version = lineValues[ 1 ]
  136.  
  137. break
  138.  
  139. case 'comment':
  140.  
  141. header.comments.push(line)
  142.  
  143. break
  144.  
  145. case 'element':
  146.  
  147. if (currentElement !== undefined) {
  148. header.elements.push(currentElement)
  149. }
  150.  
  151. currentElement = {}
  152. currentElement.name = lineValues[ 0 ]
  153. currentElement.count = parseInt(lineValues[ 1 ])
  154. currentElement.properties = []
  155.  
  156. break
  157.  
  158. case 'property':
  159.  
  160. currentElement.properties.push(makePlyElementProperty(lineValues, this.propertyNameMapping))
  161.  
  162. break
  163.  
  164. default:
  165.  
  166. console.log('unhandled', lineType, lineValues)
  167. }
  168. }
  169.  
  170. if (currentElement !== undefined) {
  171. header.elements.push(currentElement)
  172. }
  173.  
  174. return header
  175. },
  176.  
  177. parseASCIINumber: function (n, type) {
  178. switch (type) {
  179. case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint':
  180. case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32':
  181.  
  182. return parseInt(n)
  183.  
  184. case 'float': case 'double': case 'float32': case 'float64':
  185.  
  186. return parseFloat(n)
  187. }
  188. },
  189.  
  190. parseASCIIElement: function (properties, line) {
  191. var values = line.split(/\s+/)
  192.  
  193. var element = {}
  194.  
  195. for (var i = 0; i < properties.length; i++) {
  196. if (properties[ i ].type === 'list') {
  197. var list = []
  198. var n = this.parseASCIINumber(values.shift(), properties[ i ].countType)
  199.  
  200. for (var j = 0; j < n; j++) {
  201. list.push(this.parseASCIINumber(values.shift(), properties[ i ].itemType))
  202. }
  203.  
  204. element[ properties[ i ].name ] = list
  205. } else {
  206. element[ properties[ i ].name ] = this.parseASCIINumber(values.shift(), properties[ i ].type)
  207. }
  208. }
  209.  
  210. return element
  211. },
  212.  
  213. parseASCII: function (data) {
  214. // PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format)
  215.  
  216. var geometry = new Geometry()
  217.  
  218. var result
  219.  
  220. var header = this.parseHeader(data)
  221.  
  222. var patternBody = /end_header\s([\s\S]*)$/
  223. var body = ''
  224. if ((result = patternBody.exec(data)) !== null) {
  225. body = result[ 1 ]
  226. }
  227.  
  228. var lines = body.split('\n')
  229. var currentElement = 0
  230. var currentElementCount = 0
  231. geometry.useColor = false
  232.  
  233. for (var i = 0; i < lines.length; i++) {
  234. var line = lines[ i ]
  235. line = line.trim()
  236. if (line === '') {
  237. continue
  238. }
  239.  
  240. if (currentElementCount >= header.elements[ currentElement ].count) {
  241. currentElement++
  242. currentElementCount = 0
  243. }
  244.  
  245. var element = this.parseASCIIElement(header.elements[ currentElement ].properties, line)
  246.  
  247. this.handleElement(geometry, header.elements[ currentElement ].name, element)
  248.  
  249. currentElementCount++
  250. }
  251.  
  252. return this.postProcess(geometry)
  253. },
  254.  
  255. postProcess: function (geometry) {
  256. if (geometry.useColor) {
  257. for (var i = 0; i < geometry.faces.length; i++) {
  258. geometry.faces[ i ].vertexColors = [
  259. geometry.colors[ geometry.faces[ i ].a ],
  260. geometry.colors[ geometry.faces[ i ].b ],
  261. geometry.colors[ geometry.faces[ i ].c ]
  262. ]
  263. }
  264.  
  265. geometry.elementsNeedUpdate = true
  266. }
  267.  
  268. geometry.computeBoundingSphere()
  269.  
  270. return geometry
  271. },
  272.  
  273. handleElement: function (geometry, elementName, element) {
  274. if (elementName === 'vertex') {
  275. geometry.vertices.push(
  276. new Vector3(element.x, element.y, element.z)
  277. )
  278.  
  279. if ('red' in element && 'green' in element && 'blue' in element) {
  280. geometry.useColor = true
  281.  
  282. var color = new Color()
  283. color.setRGB(element.red / 255.0, element.green / 255.0, element.blue / 255.0)
  284. geometry.colors.push(color)
  285. }
  286. } else if (elementName === 'face') {
  287. var vertexIndices = element.vertex_indices
  288.  
  289. if (vertexIndices.length === 3) {
  290. geometry.faces.push(
  291. new Face3(vertexIndices[ 0 ], vertexIndices[ 1 ], vertexIndices[ 2 ])
  292. )
  293. } else if (vertexIndices.length === 4) {
  294. geometry.faces.push(
  295. new Face3(vertexIndices[ 0 ], vertexIndices[ 1 ], vertexIndices[ 3 ]),
  296. new Face3(vertexIndices[ 1 ], vertexIndices[ 2 ], vertexIndices[ 3 ])
  297. )
  298. }
  299. }
  300. },
  301.  
  302. binaryRead: function (dataview, at, type, littleEndian) {
  303. switch (type) {
  304. // corespondences for non-specific length types here match rply:
  305. case 'int8': case 'char': return [ dataview.getInt8(at), 1 ]
  306.  
  307. case 'uint8': case 'uchar': return [ dataview.getUint8(at), 1 ]
  308.  
  309. case 'int16': case 'short': return [ dataview.getInt16(at, littleEndian), 2 ]
  310.  
  311. case 'uint16': case 'ushort': return [ dataview.getUint16(at, littleEndian), 2 ]
  312.  
  313. case 'int32': case 'int': return [ dataview.getInt32(at, littleEndian), 4 ]
  314.  
  315. case 'uint32': case 'uint': return [ dataview.getUint32(at, littleEndian), 4 ]
  316.  
  317. case 'float32': case 'float': return [ dataview.getFloat32(at, littleEndian), 4 ]
  318.  
  319. case 'float64': case 'double': return [ dataview.getFloat64(at, littleEndian), 8 ]
  320. }
  321. },
  322.  
  323. binaryReadElement: function (dataview, at, properties, littleEndian) {
  324. var element = {}
  325. var result
  326. var read = 0
  327.  
  328. for (var i = 0; i < properties.length; i++) {
  329. if (properties[ i ].type === 'list') {
  330. var list = []
  331.  
  332. result = this.binaryRead(dataview, at + read, properties[ i ].countType, littleEndian)
  333. var n = result[ 0 ]
  334. read += result[ 1 ]
  335.  
  336. for (var j = 0; j < n; j++) {
  337. result = this.binaryRead(dataview, at + read, properties[ i ].itemType, littleEndian)
  338. list.push(result[ 0 ])
  339. read += result[ 1 ]
  340. }
  341.  
  342. element[ properties[ i ].name ] = list
  343. } else {
  344. result = this.binaryRead(dataview, at + read, properties[ i ].type, littleEndian)
  345. element[ properties[ i ].name ] = result[ 0 ]
  346. read += result[ 1 ]
  347. }
  348. }
  349.  
  350. return [ element, read ]
  351. },
  352.  
  353. parseBinary: function (data) {
  354. var geometry = new Geometry()
  355.  
  356. var header = this.parseHeader(this.bin2str(data))
  357. var littleEndian = (header.format === 'binary_little_endian')
  358. var body = new DataView(data, header.headerLength)
  359. var result
  360. var loc = 0
  361.  
  362. for (var currentElement = 0; currentElement < header.elements.length; currentElement++) {
  363. for (var currentElementCount = 0; currentElementCount < header.elements[ currentElement ].count; currentElementCount++) {
  364. result = this.binaryReadElement(body, loc, header.elements[ currentElement ].properties, littleEndian)
  365. loc += result[ 1 ]
  366. var element = result[ 0 ]
  367.  
  368. this.handleElement(geometry, header.elements[ currentElement ].name, element)
  369. }
  370. }
  371.  
  372. return this.postProcess(geometry)
  373. }
  374.  
  375. }
  376.  
  377. class PlyParser extends SurfaceParser {
  378. get type () { return 'ply' }
  379.  
  380. getLoader () {
  381. return new PLYLoader()
  382. }
  383. }
  384.  
  385. ParserRegistry.add('ply', PlyParser)
  386.  
  387. export default PlyParser