representation.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  7. import { Unit, Structure, StructureElement, Bond } from '../../../mol-model/structure';
  8. import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
  9. import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
  10. import { Vec3 } from '../../../mol-math/linear-algebra';
  11. import { PickingId } from '../../../mol-geo/geometry/picking';
  12. import { EmptyLoci, Loci, DataLoci } from '../../../mol-model/loci';
  13. import { Interval } from '../../../mol-data/int';
  14. import { RepresentationContext, RepresentationParamsGetter, Representation } from '../../../mol-repr/representation';
  15. import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
  16. import { VisualContext } from '../../../mol-repr/visual';
  17. import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
  18. import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../../../mol-repr/structure/units-visual';
  19. import { VisualUpdateState } from '../../../mol-repr/util';
  20. import { LocationIterator } from '../../../mol-geo/util/location-iterator';
  21. import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from './prop';
  22. import { CustomProperty } from '../../../mol-model-props/common/custom-property';
  23. import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
  24. import { Color } from '../../../mol-util/color';
  25. import { MarkerActions } from '../../../mol-util/marker-action';
  26. import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
  27. import { Sphere3D } from '../../../mol-math/geometry';
  28. import { bondLabel } from '../../../mol-theme/label';
  29. import { getUnitKindsParam } from '../../../mol-repr/structure/params';
  30. import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
  31. //
  32. function createIntraUnitClashCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitClashParams>, mesh?: Mesh) {
  33. if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
  34. const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
  35. const { edgeCount, a, b, edgeProps } = clashes;
  36. const { magnitude } = edgeProps;
  37. const { sizeFactor } = props;
  38. if (!edgeCount) return Mesh.createEmpty(mesh);
  39. const { elements } = unit;
  40. const pos = unit.conformation.invariantPosition;
  41. const builderProps = {
  42. linkCount: edgeCount * 2,
  43. position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
  44. pos(elements[a[edgeIndex]], posA);
  45. pos(elements[b[edgeIndex]], posB);
  46. },
  47. style: (edgeIndex: number) => LinkStyle.Disk,
  48. radius: (edgeIndex: number) => magnitude[edgeIndex] * sizeFactor,
  49. };
  50. return createLinkCylinderMesh(ctx, builderProps, props, mesh);
  51. }
  52. export const IntraUnitClashParams = {
  53. ...UnitsMeshParams,
  54. ...LinkCylinderParams,
  55. linkCap: PD.Boolean(true),
  56. sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.01 }),
  57. };
  58. export type IntraUnitClashParams = typeof IntraUnitClashParams
  59. export function IntraUnitClashVisual(materialId: number): UnitsVisual<IntraUnitClashParams> {
  60. return UnitsMeshVisual<IntraUnitClashParams>({
  61. defaultProps: PD.getDefaultValues(IntraUnitClashParams),
  62. createGeometry: createIntraUnitClashCylinderMesh,
  63. createLocationIterator: createIntraClashIterator,
  64. getLoci: getIntraClashLoci,
  65. eachLocation: eachIntraClash,
  66. setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IntraUnitClashParams>, currentProps: PD.Values<IntraUnitClashParams>) => {
  67. state.createGeometry = (
  68. newProps.sizeFactor !== currentProps.sizeFactor ||
  69. newProps.radialSegments !== currentProps.radialSegments ||
  70. newProps.linkScale !== currentProps.linkScale ||
  71. newProps.linkSpacing !== currentProps.linkSpacing ||
  72. newProps.linkCap !== currentProps.linkCap
  73. );
  74. }
  75. }, materialId);
  76. }
  77. function getIntraClashBoundingSphere(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[], boundingSphere: Sphere3D) {
  78. return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
  79. unit.conformation.position(unit.elements[clashes.a[elements[i]]], pA);
  80. unit.conformation.position(unit.elements[clashes.b[elements[i]]], pB);
  81. }, boundingSphere);
  82. }
  83. function getIntraClashLabel(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
  84. const idx = elements[0];
  85. if (idx === undefined) return '';
  86. const { edgeProps: { id, magnitude, distance } } = clashes;
  87. const mag = magnitude[idx].toFixed(2);
  88. const dist = distance[idx].toFixed(2);
  89. return [
  90. `Clash id: ${id[idx]} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
  91. bondLabel(Bond.Location(structure, unit, clashes.a[idx], structure, unit, clashes.b[idx]))
  92. ].join('</br>');
  93. }
  94. function IntraClashLoci(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
  95. return DataLoci('intra-clashes', { unit, clashes }, elements,
  96. (boundingSphere: Sphere3D) => getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere),
  97. () => getIntraClashLabel(structure, unit, clashes, elements));
  98. }
  99. function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
  100. const { objectId, instanceId, groupId } = pickingId;
  101. if (id === objectId) {
  102. const { structure, group } = structureGroup;
  103. const unit = group.units[instanceId];
  104. if (Unit.isAtomic(unit)) {
  105. const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
  106. return IntraClashLoci(structure, unit, clashes, [groupId]);
  107. }
  108. }
  109. return EmptyLoci;
  110. }
  111. function eachIntraClash(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
  112. const changed = false;
  113. // TODO
  114. return changed;
  115. }
  116. function createIntraClashIterator(structureGroup: StructureGroup): LocationIterator {
  117. const { structure, group } = structureGroup;
  118. const unit = group.units[0];
  119. const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
  120. const { a } = clashes;
  121. const groupCount = clashes.edgeCount * 2;
  122. const instanceCount = group.units.length;
  123. const location = StructureElement.Location.create(structure);
  124. const getLocation = (groupIndex: number, instanceIndex: number) => {
  125. const unit = group.units[instanceIndex];
  126. location.unit = unit;
  127. location.element = unit.elements[a[groupIndex]];
  128. return location;
  129. };
  130. return LocationIterator(groupCount, instanceCount, 1, getLocation);
  131. }
  132. //
  133. function createInterUnitClashCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitClashParams>, mesh?: Mesh) {
  134. const clashes = ClashesProvider.get(structure).value!.interUnit;
  135. const { edges, edgeCount } = clashes;
  136. const { sizeFactor } = props;
  137. if (!edgeCount) return Mesh.createEmpty(mesh);
  138. const builderProps = {
  139. linkCount: edgeCount,
  140. position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
  141. const b = edges[edgeIndex];
  142. const uA = structure.unitMap.get(b.unitA);
  143. const uB = structure.unitMap.get(b.unitB);
  144. uA.conformation.position(uA.elements[b.indexA], posA);
  145. uB.conformation.position(uB.elements[b.indexB], posB);
  146. },
  147. style: (edgeIndex: number) => LinkStyle.Disk,
  148. radius: (edgeIndex: number) => edges[edgeIndex].props.magnitude * sizeFactor
  149. };
  150. return createLinkCylinderMesh(ctx, builderProps, props, mesh);
  151. }
  152. export const InterUnitClashParams = {
  153. ...ComplexMeshParams,
  154. ...LinkCylinderParams,
  155. linkCap: PD.Boolean(true),
  156. sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.01 }),
  157. };
  158. export type InterUnitClashParams = typeof InterUnitClashParams
  159. export function InterUnitClashVisual(materialId: number): ComplexVisual<InterUnitClashParams> {
  160. return ComplexMeshVisual<InterUnitClashParams>({
  161. defaultProps: PD.getDefaultValues(InterUnitClashParams),
  162. createGeometry: createInterUnitClashCylinderMesh,
  163. createLocationIterator: createInterClashIterator,
  164. getLoci: getInterClashLoci,
  165. eachLocation: eachInterClash,
  166. setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitClashParams>, currentProps: PD.Values<InterUnitClashParams>) => {
  167. state.createGeometry = (
  168. newProps.sizeFactor !== currentProps.sizeFactor ||
  169. newProps.radialSegments !== currentProps.radialSegments ||
  170. newProps.linkScale !== currentProps.linkScale ||
  171. newProps.linkSpacing !== currentProps.linkSpacing ||
  172. newProps.linkCap !== currentProps.linkCap
  173. );
  174. }
  175. }, materialId);
  176. }
  177. function getInterClashBoundingSphere(structure: Structure, clashes: InterUnitClashes, elements: number[], boundingSphere: Sphere3D) {
  178. return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
  179. const c = clashes.edges[elements[i]];
  180. const uA = structure.unitMap.get(c.unitA);
  181. const uB = structure.unitMap.get(c.unitB);
  182. uA.conformation.position(uA.elements[c.indexA], pA);
  183. uB.conformation.position(uB.elements[c.indexB], pB);
  184. }, boundingSphere);
  185. }
  186. function getInterClashLabel(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
  187. const idx = elements[0];
  188. if (idx === undefined) return '';
  189. const c = clashes.edges[idx];
  190. const uA = structure.unitMap.get(c.unitA);
  191. const uB = structure.unitMap.get(c.unitB);
  192. const mag = c.props.magnitude.toFixed(2);
  193. const dist = c.props.distance.toFixed(2);
  194. return [
  195. `Clash id: ${c.props.id} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
  196. bondLabel(Bond.Location(structure, uA, c.indexA, structure, uB, c.indexB))
  197. ].join('</br>');
  198. }
  199. function InterClashLoci(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
  200. return DataLoci('inter-clashes', clashes, elements,
  201. (boundingSphere: Sphere3D) => getInterClashBoundingSphere(structure, clashes, elements, boundingSphere),
  202. () => getInterClashLabel(structure, clashes, elements));
  203. }
  204. function getInterClashLoci(pickingId: PickingId, structure: Structure, id: number) {
  205. const { objectId, groupId } = pickingId;
  206. if (id === objectId) {
  207. const clashes = ClashesProvider.get(structure).value!.interUnit;
  208. return InterClashLoci(structure, clashes, [groupId]);
  209. }
  210. return EmptyLoci;
  211. }
  212. function eachInterClash(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
  213. const changed = false;
  214. // TODO
  215. return changed;
  216. }
  217. function createInterClashIterator(structure: Structure): LocationIterator {
  218. const clashes = ClashesProvider.get(structure).value!.interUnit;
  219. const groupCount = clashes.edgeCount;
  220. const instanceCount = 1;
  221. const location = StructureElement.Location.create(structure);
  222. const getLocation = (groupIndex: number) => {
  223. const clash = clashes.edges[groupIndex];
  224. location.unit = structure.unitMap.get(clash.unitA);
  225. location.element = location.unit.elements[clash.indexA];
  226. return location;
  227. };
  228. return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
  229. }
  230. //
  231. const ClashesVisuals = {
  232. 'intra-clash': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitClashParams>) => UnitsRepresentation('Intra-unit clash cylinder', ctx, getParams, IntraUnitClashVisual),
  233. 'inter-clash': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitClashParams>) => ComplexRepresentation('Inter-unit clash cylinder', ctx, getParams, InterUnitClashVisual),
  234. };
  235. export const ClashesParams = {
  236. ...IntraUnitClashParams,
  237. ...InterUnitClashParams,
  238. unitKinds: getUnitKindsParam(['atomic']),
  239. visuals: PD.MultiSelect(['intra-clash', 'inter-clash'], PD.objectToOptions(ClashesVisuals))
  240. };
  241. export type ClashesParams = typeof ClashesParams
  242. export function getClashesParams(ctx: ThemeRegistryContext, structure: Structure) {
  243. return PD.clone(ClashesParams);
  244. }
  245. export type ClashesRepresentation = StructureRepresentation<ClashesParams>
  246. export function ClashesRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ClashesParams>): ClashesRepresentation {
  247. const repr = Representation.createMulti('Clashes', ctx, getParams, StructureRepresentationStateBuilder, ClashesVisuals as unknown as Representation.Def<Structure, ClashesParams>);
  248. repr.setState({ markerActions: MarkerActions.Highlighting });
  249. return repr;
  250. }
  251. export const ClashesRepresentationProvider = StructureRepresentationProvider({
  252. name: ValidationReport.Tag.Clashes,
  253. label: 'Validation Clashes',
  254. description: 'Displays clashes between atoms as disks. Data from wwPDB Validation Report, obtained via RCSB PDB.',
  255. factory: ClashesRepresentation,
  256. getParams: getClashesParams,
  257. defaultValues: PD.getDefaultValues(ClashesParams),
  258. defaultColorTheme: { name: 'uniform', props: { value: Color(0xFA28FF) } },
  259. defaultSizeTheme: { name: 'physical' },
  260. isApplicable: (structure: Structure) => structure.elementCount > 0,
  261. ensureCustomProperties: {
  262. attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
  263. detach: (data) => ClashesProvider.ref(data, false)
  264. }
  265. });