ply.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Schäfer, Marco <marco.schaefer@uni-tuebingen.de>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { RuntimeContext, Task } from 'mol-task';
  8. import { ShapeProvider } from 'mol-model/shape/provider';
  9. import { Color } from 'mol-util/color';
  10. import { PlyFile, PlyTable, PlyList } from 'mol-io/reader/ply/schema';
  11. import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
  12. import { Mesh } from 'mol-geo/geometry/mesh/mesh';
  13. import { Shape } from 'mol-model/shape';
  14. import { ChunkedArray } from 'mol-data/util';
  15. import { arrayMax, fillSerial } from 'mol-util/array';
  16. import { Column } from 'mol-data/db';
  17. import { ParamDefinition as PD } from 'mol-util/param-definition';
  18. import { ColorNames } from 'mol-util/color/tables';
  19. // TODO support 'edge' and 'material' elements, see https://www.mathworks.com/help/vision/ug/the-ply-format.html
  20. async function getPlyMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList, groupIds: ArrayLike<number>, mesh?: Mesh) {
  21. const builderState = MeshBuilder.createState(vertex.rowCount, vertex.rowCount / 4, mesh)
  22. const { vertices, normals, indices, groups } = builderState
  23. const x = vertex.getProperty('x')
  24. const y = vertex.getProperty('y')
  25. const z = vertex.getProperty('z')
  26. if (!x || !y || !z) throw new Error('missing coordinate properties')
  27. const nx = vertex.getProperty('nx')
  28. const ny = vertex.getProperty('ny')
  29. const nz = vertex.getProperty('nz')
  30. const hasNormals = !!nx && !!ny && !!nz
  31. for (let i = 0, il = vertex.rowCount; i < il; ++i) {
  32. if (i % 10000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding vertex ${i}` })
  33. ChunkedArray.add3(vertices, x.value(i), y.value(i), z.value(i))
  34. if (hasNormals) ChunkedArray.add3(normals, nx!.value(i), ny!.value(i), nz!.value(i));
  35. ChunkedArray.add(groups, groupIds[i])
  36. }
  37. for (let i = 0, il = face.rowCount; i < il; ++i) {
  38. if (i % 10000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding face ${i}` })
  39. const { entries } = face.value(i)
  40. ChunkedArray.add3(indices, entries[0], entries[1], entries[2])
  41. }
  42. const m = MeshBuilder.getMesh(builderState);
  43. m.normalsComputed = hasNormals
  44. await Mesh.computeNormals(m).runInContext(ctx)
  45. return m
  46. }
  47. function getGrouping(count: number, column?: Column<number>) {
  48. const ids = column ? column.toArray({ array: Int32Array }) : fillSerial(new Uint32Array(count))
  49. const maxId = arrayMax(ids) // assumes uint ids
  50. const map = new Uint32Array(maxId + 1)
  51. for (let i = 0, il = ids.length; i < il; ++i) map[ids[i]] = i
  52. return { ids, map }
  53. }
  54. async function getShape(ctx: RuntimeContext, plyFile: PlyFile, props: PD.Values<PlyShapeParams>, shape?: Shape<Mesh>) {
  55. await ctx.update('async creation of shape from ply file')
  56. const { coloring, grouping } = props
  57. const vertex = plyFile.getElement('vertex') as PlyTable
  58. if (!vertex) throw new Error('missing vertex element')
  59. const { rowCount } = vertex
  60. const int = Column.Schema.int
  61. let red: Column<number>, green: Column<number>, blue: Column<number>
  62. if (coloring.name === 'vertex') {
  63. red = vertex.getProperty(coloring.params.red) || Column.ofConst(127, rowCount, int)
  64. green = vertex.getProperty(coloring.params.green) || Column.ofConst(127, rowCount, int)
  65. blue = vertex.getProperty(coloring.params.blue) || Column.ofConst(127, rowCount, int)
  66. } else {
  67. const [r, g, b] = Color.toRgb(coloring.params.color)
  68. red = Column.ofConst(r, rowCount, int)
  69. green = Column.ofConst(g, rowCount, int)
  70. blue = Column.ofConst(b, rowCount, int)
  71. }
  72. const face = plyFile.getElement('face') as PlyList
  73. if (!face) throw new Error('missing face element')
  74. const { ids, map } = getGrouping(vertex.rowCount, grouping.name === 'vertex' ? vertex.getProperty(grouping.params.group) : undefined)
  75. const mesh = await getPlyMesh(ctx, vertex, face, ids, shape && shape.geometry)
  76. return Shape.create(
  77. 'test', plyFile, mesh,
  78. (groupId: number) => {
  79. const idx = map[groupId]
  80. return Color.fromRgb(red.value(idx), green.value(idx), blue.value(idx))
  81. },
  82. () => 1, // size: constant
  83. (groupId: number) => {
  84. return ids[groupId].toString()
  85. }
  86. )
  87. }
  88. function createPlyShapeParams(vertex?: PlyTable) {
  89. const options: [string, string][] = [['', '']]
  90. const defaultValues = { group: '', red: '', green: '', blue: '' }
  91. if (vertex) {
  92. for (let i = 0, il = vertex.propertyNames.length; i < il; ++i) {
  93. const name = vertex.propertyNames[i]
  94. options.push([ name, name ])
  95. }
  96. // TODO harcoded as convenience for data provided by MegaMol
  97. if (vertex.propertyNames.includes('atomid')) defaultValues.group = 'atomid'
  98. if (vertex.propertyNames.includes('red')) defaultValues.red = 'red'
  99. if (vertex.propertyNames.includes('green')) defaultValues.green = 'green'
  100. if (vertex.propertyNames.includes('blue')) defaultValues.blue = 'blue'
  101. }
  102. return {
  103. ...Mesh.Params,
  104. coloring: PD.MappedStatic(defaultValues.red && defaultValues.green && defaultValues.blue ? 'vertex' : 'uniform', {
  105. vertex: PD.Group({
  106. red: PD.Select(defaultValues.red, options, { label: 'Red Property' }),
  107. green: PD.Select(defaultValues.green, options, { label: 'Green Property' }),
  108. blue: PD.Select(defaultValues.blue, options, { label: 'Blue Property' }),
  109. }, { isFlat: true }),
  110. uniform: PD.Group({
  111. color: PD.Color(ColorNames.grey)
  112. }, { isFlat: true })
  113. }),
  114. grouping: PD.MappedStatic(defaultValues.group ? 'vertex' : 'none', {
  115. vertex: PD.Group({
  116. group: PD.Select(defaultValues.group, options, { label: 'Group Property' }),
  117. }, { isFlat: true }),
  118. none: PD.Group({ })
  119. }),
  120. }
  121. }
  122. export const PlyShapeParams = createPlyShapeParams()
  123. export type PlyShapeParams = typeof PlyShapeParams
  124. export function shapeFromPly(source: PlyFile, params?: {}) {
  125. return Task.create<ShapeProvider<PlyFile, Mesh, PlyShapeParams>>('Shape Provider', async ctx => {
  126. return {
  127. label: 'Mesh',
  128. data: source,
  129. params: createPlyShapeParams(source.getElement('vertex') as PlyTable),
  130. getShape,
  131. geometryUtils: Mesh.Utils
  132. }
  133. })
  134. }