mesh-extension.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /**
  2. * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Adam Midlik <midlik@gmail.com>
  5. */
  6. /** Defines new types of State tree transformers for dealing with mesh data. */
  7. import { BaseGeometry, VisualQuality, VisualQualityOptions } from '../../mol-geo/geometry/base';
  8. import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
  9. import { CifFile } from '../../mol-io/reader/cif';
  10. import { Box3D } from '../../mol-math/geometry';
  11. import { Vec3 } from '../../mol-math/linear-algebra';
  12. import { Shape } from '../../mol-model/shape';
  13. import { ShapeProvider } from '../../mol-model/shape/provider';
  14. import { PluginStateObject } from '../../mol-plugin-state/objects';
  15. import { StateTransforms } from '../../mol-plugin-state/transforms';
  16. import { Download } from '../../mol-plugin-state/transforms/data';
  17. import { ShapeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
  18. import { PluginContext } from '../../mol-plugin/context';
  19. import { StateObjectRef, StateObjectSelector, StateTransformer } from '../../mol-state';
  20. import { Task } from '../../mol-task';
  21. import { Color } from '../../mol-util/color';
  22. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  23. import * as MeshUtils from './mesh-utils';
  24. export const BACKGROUND_OPACITY = 0.2;
  25. export const FOREROUND_OPACITY = 1;
  26. export const VolsegTransform: StateTransformer.Builder.Root = StateTransformer.builderFactory('volseg');
  27. // // // // // // // // // // // // // // // // // // // // // // // //
  28. // Parsed data
  29. /** Data type for `MeshlistStateObject` - list of meshes */
  30. export interface MeshlistData {
  31. segmentId: number,
  32. segmentName: string,
  33. detail: number,
  34. meshIds: number[],
  35. mesh: Mesh,
  36. /** Reference to the object which created this meshlist (e.g. `MeshStreaming.Behavior`) */
  37. ownerId?: string,
  38. }
  39. export namespace MeshlistData {
  40. export function empty(): MeshlistData {
  41. return {
  42. segmentId: 0,
  43. segmentName: 'Empty',
  44. detail: 0,
  45. meshIds: [],
  46. mesh: Mesh.createEmpty(),
  47. };
  48. };
  49. export async function fromCIF(data: CifFile, segmentId: number, segmentName: string, detail: number): Promise<MeshlistData> {
  50. const { mesh, meshIds } = await MeshUtils.meshFromCif(data);
  51. return {
  52. segmentId,
  53. segmentName,
  54. detail,
  55. meshIds,
  56. mesh,
  57. };
  58. }
  59. export function stats(meshListData: MeshlistData): string {
  60. return `Meshlist "${meshListData.segmentName}" (detail ${meshListData.detail}): ${meshListData.meshIds.length} meshes, ${meshListData.mesh.vertexCount} vertices, ${meshListData.mesh.triangleCount} triangles`;
  61. }
  62. export function getShape(data: MeshlistData, color: Color): Shape<Mesh> {
  63. const mesh = data.mesh;
  64. const meshShape: Shape<Mesh> = Shape.create(data.segmentName, data, mesh,
  65. () => color,
  66. () => 1,
  67. // group => `${data.segmentName} | Segment ${data.segmentId} | Detail ${data.detail} | Mesh ${group}`,
  68. group => data.segmentName,
  69. );
  70. return meshShape;
  71. }
  72. export function combineBBoxes(boxes: (Box3D | null)[]): Box3D | null {
  73. let result = null;
  74. for (const box of boxes) {
  75. if (!box) continue;
  76. if (result) {
  77. Vec3.min(result.min, result.min, box.min);
  78. Vec3.max(result.max, result.max, box.max);
  79. } else {
  80. result = Box3D.zero();
  81. Box3D.copy(result, box);
  82. }
  83. }
  84. return result;
  85. }
  86. export function bbox(data: MeshlistData): Box3D | null {
  87. return MeshUtils.bbox(data.mesh);
  88. }
  89. export function allVerticesUsed(data: MeshlistData): boolean {
  90. const unusedVertices = new Set();
  91. for (let i = 0; i < data.mesh.vertexCount; i++) {
  92. unusedVertices.add(i);
  93. }
  94. for (let i = 0; i < 3 * data.mesh.triangleCount; i++) {
  95. const v = data.mesh.vertexBuffer.ref.value[i];
  96. unusedVertices.delete(v);
  97. }
  98. return unusedVertices.size === 0;
  99. }
  100. }
  101. // // // // // // // // // // // // // // // // // // // // // // // //
  102. // Raw Data -> Parsed data
  103. export class MeshlistStateObject extends PluginStateObject.Create<MeshlistData>({ name: 'Parsed Meshlist', typeClass: 'Object' }) { }
  104. export const ParseMeshlistTransformer = VolsegTransform({
  105. name: 'meshlist-from-string',
  106. from: PluginStateObject.Format.Cif,
  107. to: MeshlistStateObject,
  108. params: {
  109. label: PD.Text(MeshlistStateObject.type.name, { isHidden: true }),
  110. segmentId: PD.Numeric(1, {}, { isHidden: true }),
  111. segmentName: PD.Text('Segment'),
  112. detail: PD.Numeric(1, {}, { isHidden: true }),
  113. /** Reference to the object which manages this meshlist (e.g. `MeshStreaming.Behavior`) */
  114. ownerId: PD.Text('', { isHidden: true }),
  115. }
  116. })({
  117. apply({ a, params }, globalCtx) { // `a` is the parent node, params are 2nd argument to To.apply(), `globalCtx` is the plugin
  118. return Task.create('Create Parsed Meshlist', async ctx => {
  119. const meshlistData = await MeshlistData.fromCIF(a.data, params.segmentId, params.segmentName, params.detail);
  120. meshlistData.ownerId = params.ownerId;
  121. const es = meshlistData.meshIds.length === 1 ? '' : 'es';
  122. return new MeshlistStateObject(meshlistData, { label: params.label, description: `${meshlistData.segmentName} (${meshlistData.meshIds.length} mesh${es})` });
  123. });
  124. }
  125. });
  126. // // // // // // // // // // // // // // // // // // // // // // // //
  127. // Parsed data -> Shape
  128. /** Data type for PluginStateObject.Shape.Provider */
  129. type MeshShapeProvider = ShapeProvider<MeshlistData, Mesh, Mesh.Params>;
  130. namespace MeshShapeProvider {
  131. export function fromMeshlistData(meshlist: MeshlistData, color?: Color): MeshShapeProvider {
  132. const theColor = color ?? MeshUtils.ColorGenerator.next().value;
  133. return {
  134. label: 'Mesh',
  135. data: meshlist,
  136. params: meshShapeProviderParams,
  137. geometryUtils: Mesh.Utils,
  138. getShape: (ctx, data: MeshlistData) => MeshlistData.getShape(data, theColor),
  139. };
  140. }
  141. }
  142. const meshShapeProviderParams: Mesh.Params = {
  143. ...Mesh.Params,
  144. quality: PD.Select<VisualQuality>('custom', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }), // use 'custom' when wanting to apply doubleSided
  145. doubleSided: PD.Boolean(true, BaseGeometry.CustomQualityParamInfo),
  146. // set `flatShaded`: true to see the real mesh vertices and triangles
  147. transparentBackfaces: PD.Select('on', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory), // 'on' means: show backfaces with correct opacity, even when opacity < 1 (requires doubleSided) ¯\_(ツ)_/¯
  148. };
  149. export const MeshShapeTransformer = VolsegTransform({
  150. name: 'shape-from-meshlist',
  151. display: { name: 'Shape from Meshlist', description: 'Create Shape from Meshlist data' },
  152. from: MeshlistStateObject,
  153. to: PluginStateObject.Shape.Provider,
  154. params: {
  155. color: PD.Value<Color | undefined>(undefined), // undefined means random color
  156. },
  157. })({
  158. apply({ a, params }) {
  159. const shapeProvider = MeshShapeProvider.fromMeshlistData(a.data, params.color);
  160. return new PluginStateObject.Shape.Provider(shapeProvider, { label: PluginStateObject.Shape.Provider.type.name, description: a.description });
  161. }
  162. });
  163. // // // // // // // // // // // // // // // // // // // // // // // //
  164. /** Download data and create state tree hierarchy down to visual representation. */
  165. export async function createMeshFromUrl(plugin: PluginContext, meshDataUrl: string, segmentId: number, detail: number,
  166. collapseTree: boolean, color?: Color, parent?: StateObjectSelector | StateObjectRef, transparentIfBboxAbove?: number,
  167. name?: string, ownerId?: string) {
  168. const update = parent ? plugin.build().to(parent) : plugin.build().toRoot();
  169. const rawDataNodeRef = update.apply(Download,
  170. { url: meshDataUrl, isBinary: true, label: `Downloaded Data ${segmentId}` },
  171. { state: { isCollapsed: collapseTree } }
  172. ).ref;
  173. const parsedDataNode = await update.to(rawDataNodeRef)
  174. .apply(StateTransforms.Data.ParseCif)
  175. .apply(ParseMeshlistTransformer,
  176. { label: undefined, segmentId: segmentId, segmentName: name ?? `Segment ${segmentId}`, detail: detail, ownerId: ownerId },
  177. {}
  178. )
  179. .commit();
  180. let transparent = false;
  181. if (transparentIfBboxAbove !== undefined && parsedDataNode.data) {
  182. const bbox = MeshlistData.bbox(parsedDataNode.data) || Box3D.zero();
  183. transparent = Box3D.volume(bbox) > transparentIfBboxAbove;
  184. }
  185. await plugin.build().to(parsedDataNode)
  186. .apply(MeshShapeTransformer, { color: color },)
  187. .apply(ShapeRepresentation3D,
  188. { alpha: transparent ? BACKGROUND_OPACITY : FOREROUND_OPACITY },
  189. { tags: ['mesh-segment-visual', `segment-${segmentId}`] }
  190. )
  191. .commit();
  192. return rawDataNodeRef;
  193. }