structure-labels.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Structure, StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
  7. import { StateTransformer } from '../../mol-state';
  8. import { StructureLabels3D } from '../state/transforms/representation';
  9. import { ShapeRepresentation } from '../../mol-repr/shape/representation';
  10. import { Vec3 } from '../../mol-math/linear-algebra';
  11. import { Text } from '../../mol-geo/geometry/text/text';
  12. import { TextBuilder } from '../../mol-geo/geometry/text/text-builder';
  13. import { Shape } from '../../mol-model/shape';
  14. import { ColorNames } from '../../mol-util/color/names';
  15. import { RuntimeContext } from '../../mol-task';
  16. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  17. import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
  18. interface LabelsData {
  19. texts: string[],
  20. positions: Vec3[],
  21. sizes: number[],
  22. depths: number[]
  23. }
  24. function getLabelsText(data: LabelsData, props: PD.Values<Text.Params>, text?: Text) {
  25. const { texts, positions, depths } = data
  26. const textBuilder = TextBuilder.create(props, texts.length * 10, texts.length * 10 / 2, text)
  27. for (let i = 0, il = texts.length; i < il; ++i) {
  28. const p = positions[i]
  29. textBuilder.add(texts[i], p[0], p[1], p[2], depths[i], i)
  30. }
  31. return textBuilder.getText()
  32. }
  33. export async function getLabelRepresentation(ctx: RuntimeContext, structure: Structure, params: StateTransformer.Params<StructureLabels3D>, prev?: ShapeRepresentation<LabelsData, Text, Text.Params>) {
  34. const repr = prev || ShapeRepresentation(getLabelsShape, Text.Utils);
  35. const data = getLabelData(structure, params);
  36. await repr.createOrUpdate(params.options, data).runInContext(ctx);
  37. repr.setState({ pickable: false })
  38. return repr;
  39. }
  40. function getLabelsShape(ctx: RuntimeContext, data: LabelsData, props: PD.Values<Text.Params>, shape?: Shape<Text>) {
  41. const geo = getLabelsText(data, props, shape && shape.geometry);
  42. return Shape.create('Scene Labels', data, geo, () => ColorNames.dimgrey, g => data.sizes[g], () => '')
  43. }
  44. const boundaryHelper = new BoundaryHelper();
  45. function getLabelData(structure: Structure, params: StateTransformer.Params<StructureLabels3D>): LabelsData {
  46. if (params.target.name === 'static-text') {
  47. return getLabelDataStatic(structure, params.target.params.value, params.target.params.size || 1, params.target.params.position || 'middle-center');
  48. } else {
  49. return getLabelDataComputed(structure, params.target.name);
  50. }
  51. }
  52. function getLabelDataStatic(structure: Structure, text: string, size: number, position: Text.Params['attachment']['defaultValue']): LabelsData {
  53. const boundary = structure.boundary.sphere;
  54. let oX = 0, oY = 0;
  55. if (position.indexOf('left') >= 0) oX = -boundary.radius;
  56. if (position.indexOf('right') >= 0) oX = boundary.radius;
  57. if (position.indexOf('top') >= 0) oY = boundary.radius;
  58. if (position.indexOf('bottom') >= 0) oY = -boundary.radius;
  59. return {
  60. texts: [text],
  61. positions: [Vec3.add(Vec3.zero(), boundary.center, Vec3.create(oX, oY, 0))],
  62. sizes: [size],
  63. depths: [boundary.radius + Math.sqrt(oX * oX + oY * oY)]
  64. };
  65. }
  66. function getLabelDataComputed(structure: Structure, level: 'elements' | 'residues'): LabelsData {
  67. const data: LabelsData = { texts: [], positions: [], sizes: [], depths: [] };
  68. const l = StructureElement.Location.create();
  69. const { units } = structure;
  70. const { label_atom_id } = StructureProperties.atom;
  71. const { auth_seq_id, label_comp_id } = StructureProperties.residue;
  72. const { auth_asym_id } = StructureProperties.chain;
  73. const p = Vec3.zero();
  74. for (const unit of units) {
  75. // TODO: support coarse models
  76. if (unit.kind !== Unit.Kind.Atomic) continue;
  77. l.unit = unit;
  78. const elements = unit.elements;
  79. const pos = unit.conformation.position;
  80. if (level === 'elements') {
  81. for (let j = 0, _j = elements.length; j < _j; j++) {
  82. l.element = elements[j];
  83. pos(l.element, p);
  84. data.texts.push(label_atom_id(l));
  85. data.positions.push(Vec3.clone(p));
  86. data.sizes.push(1);
  87. data.depths.push(2);
  88. }
  89. } else {
  90. const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
  91. let i = 0, len = elements.length;
  92. while (i < len) {
  93. const start = i, rI = residueIndex[elements[i]];
  94. i++;
  95. while (i < len && residueIndex[elements[i]] === rI) i++;
  96. boundaryHelper.reset(0);
  97. for (let eI = start; eI < i; eI++) {
  98. pos(elements[eI], p);
  99. boundaryHelper.boundaryStep(p, 0);
  100. }
  101. boundaryHelper.finishBoundaryStep();
  102. for (let eI = start; eI < i; eI++) {
  103. pos(elements[eI], p);
  104. boundaryHelper.extendStep(p, 0);
  105. }
  106. l.element = elements[start];
  107. data.texts.push(`${label_comp_id(l)} ${auth_seq_id(l)}:${auth_asym_id(l)}`);
  108. data.positions.push(Vec3.clone(boundaryHelper.center));
  109. data.sizes.push(Math.max(1, boundaryHelper.radius / 5));
  110. data.depths.push(boundaryHelper.radius);
  111. }
  112. }
  113. }
  114. return data;
  115. }