units-representation.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /**
  2. * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  8. import { StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationState } from './representation';
  9. import { Visual } from '../visual';
  10. import { Representation, RepresentationContext, RepresentationParamsGetter } from '../representation';
  11. import { Structure, Unit, StructureElement, Bond } from '../../mol-model/structure';
  12. import { Subject } from 'rxjs';
  13. import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
  14. import { Theme } from '../../mol-theme/theme';
  15. import { Task } from '../../mol-task';
  16. import { PickingId } from '../../mol-geo/geometry/picking';
  17. import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci, EveryLoci } from '../../mol-model/loci';
  18. import { MarkerAction, MarkerActions, applyMarkerAction } from '../../mol-util/marker-action';
  19. import { Overpaint } from '../../mol-theme/overpaint';
  20. import { Transparency } from '../../mol-theme/transparency';
  21. import { Mat4, EPSILON } from '../../mol-math/linear-algebra';
  22. import { Interval } from '../../mol-data/int';
  23. import { StructureParams } from './params';
  24. import { Clipping } from '../../mol-theme/clipping';
  25. import { WebGLContext } from '../../mol-gl/webgl/context';
  26. import { StructureGroup } from './visual/util/common';
  27. import { Substance } from '../../mol-theme/substance';
  28. import { LocationCallback } from '../util';
  29. export interface UnitsVisual<P extends StructureParams> extends Visual<StructureGroup, P> { }
  30. export function UnitsRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, structure: Structure, props: PD.Values<P>, webgl?: WebGLContext) => UnitsVisual<P>): StructureRepresentation<P> {
  31. let version = 0;
  32. const { webgl } = ctx;
  33. const updated = new Subject<number>();
  34. const materialId = getNextMaterialId();
  35. const renderObjects: GraphicsRenderObject[] = [];
  36. const geometryState = new Representation.GeometryState();
  37. const _state = StructureRepresentationStateBuilder.create();
  38. let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>();
  39. let _structure: Structure;
  40. let _groups: ReadonlyArray<Unit.SymmetryGroup>;
  41. let _params: P;
  42. let _props: PD.Values<P>;
  43. let _theme = Theme.createEmpty();
  44. function createOrUpdate(props: Partial<PD.Values<P>> = {}, structure?: Structure) {
  45. if (structure && structure !== _structure) {
  46. _params = getParams(ctx, structure);
  47. if (!_props) _props = PD.getDefaultValues(_params);
  48. }
  49. _props = Object.assign({}, _props, props);
  50. return Task.create('Creating or updating UnitsRepresentation', async runtime => {
  51. if (!_structure && !structure) {
  52. throw new Error('missing structure');
  53. } else if (structure && !_structure) {
  54. // console.log(label, 'initial structure');
  55. // First call with a structure, create visuals for each group.
  56. _groups = structure.unitSymmetryGroups;
  57. for (let i = 0; i < _groups.length; i++) {
  58. const group = _groups[i];
  59. const visual = visualCtor(materialId, structure, _props, webgl);
  60. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
  61. if (promise) await promise;
  62. setVisualState(visual, group, _state); // current state for new visual
  63. visuals.set(group.hashCode, { visual, group });
  64. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
  65. }
  66. } else if (structure && (!Structure.areUnitIdsAndIndicesEqual(structure, _structure) || structure.child !== _structure.child)) {
  67. // console.log(label, 'structures not equivalent');
  68. // Tries to re-use existing visuals for the groups of the new structure.
  69. // Creates additional visuals if needed, destroys left-over visuals.
  70. _groups = structure.unitSymmetryGroups;
  71. // const newGroups: Unit.SymmetryGroup[] = []
  72. const oldVisuals = visuals;
  73. visuals = new Map();
  74. for (let i = 0; i < _groups.length; i++) {
  75. const group = _groups[i];
  76. const visualGroup = oldVisuals.get(group.hashCode);
  77. if (visualGroup) {
  78. // console.log(label, 'found visualGroup to reuse');
  79. // console.log('old', visualGroup.group)
  80. // console.log('new', group)
  81. let { visual } = visualGroup;
  82. if (visual.mustRecreate?.({ group, structure }, _props, webgl)) {
  83. visual.destroy();
  84. visual = visualCtor(materialId, structure, _props, webgl);
  85. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
  86. if (promise) await promise;
  87. setVisualState(visual, group, _state); // current state for new visual
  88. } else {
  89. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
  90. if (promise) await promise;
  91. }
  92. visuals.set(group.hashCode, { visual, group });
  93. oldVisuals.delete(group.hashCode);
  94. // Remove highlight
  95. // TODO: remove selection too??
  96. if (visual.renderObject) {
  97. const arr = visual.renderObject.values.tMarker.ref.value.array;
  98. applyMarkerAction(arr, Interval.ofBounds(0, arr.length), MarkerAction.RemoveHighlight);
  99. }
  100. } else {
  101. // console.log(label, 'did not find visualGroup to reuse, creating new');
  102. // newGroups.push(group)
  103. const visual = visualCtor(materialId, structure, _props, webgl);
  104. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
  105. if (promise) await promise;
  106. setVisualState(visual, group, _state); // current state for new visual
  107. visuals.set(group.hashCode, { visual, group });
  108. }
  109. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
  110. }
  111. oldVisuals.forEach(({ visual }) => {
  112. // console.log(label, 'removed unused visual');
  113. visual.destroy();
  114. });
  115. } else if (structure && structure !== _structure && Structure.areUnitIdsAndIndicesEqual(structure, _structure)) {
  116. // console.log(label, 'structures equivalent but not identical');
  117. // Expects that for structures with the same hashCode,
  118. // the unitSymmetryGroups are the same as well.
  119. // Re-uses existing visuals for the groups of the new structure.
  120. _groups = structure.unitSymmetryGroups;
  121. // console.log('new', structure.unitSymmetryGroups)
  122. // console.log('old', _structure.unitSymmetryGroups)
  123. for (let i = 0; i < _groups.length; i++) {
  124. const group = _groups[i];
  125. const visualGroup = visuals.get(group.hashCode);
  126. if (visualGroup) {
  127. let { visual } = visualGroup;
  128. if (visual.mustRecreate?.({ group, structure }, _props, ctx.webgl)) {
  129. visual.destroy();
  130. visual = visualCtor(materialId, structure, _props, ctx.webgl);
  131. visualGroup.visual = visual;
  132. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
  133. if (promise) await promise;
  134. setVisualState(visual, group, _state); // current state for new visual
  135. } else {
  136. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
  137. if (promise) await promise;
  138. }
  139. visualGroup.group = group;
  140. } else {
  141. throw new Error(`expected to find visual for hashCode ${group.hashCode}`);
  142. }
  143. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
  144. }
  145. } else {
  146. // console.log(label, 'no new structure');
  147. // No new structure given, just update all visuals with new props.
  148. const visualsList: { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }[] = []; // TODO avoid allocation
  149. visuals.forEach(vg => visualsList.push(vg));
  150. for (let i = 0, il = visualsList.length; i < il; ++i) {
  151. let { visual, group } = visualsList[i];
  152. if (visual.mustRecreate?.({ group, structure: _structure }, _props, ctx.webgl)) {
  153. visual.destroy();
  154. visual = visualCtor(materialId, _structure, _props, webgl);
  155. visualsList[i].visual = visual;
  156. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure: _structure });
  157. if (promise) await promise;
  158. setVisualState(visual, group, _state); // current state for new visual
  159. } else {
  160. const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props);
  161. if (promise) await promise;
  162. }
  163. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: il });
  164. }
  165. }
  166. // update list of renderObjects
  167. renderObjects.length = 0;
  168. visuals.forEach(({ visual }) => {
  169. if (visual.renderObject) {
  170. renderObjects.push(visual.renderObject);
  171. geometryState.add(visual.renderObject.id, visual.geometryVersion);
  172. }
  173. });
  174. geometryState.snapshot();
  175. // set new structure
  176. if (structure) _structure = structure;
  177. // increment version
  178. updated.next(version++);
  179. });
  180. }
  181. function getLoci(pickingId: PickingId) {
  182. let loci: Loci = EmptyLoci;
  183. visuals.forEach(({ visual }) => {
  184. const _loci = visual.getLoci(pickingId);
  185. if (!isEmptyLoci(_loci)) loci = _loci;
  186. });
  187. return loci;
  188. }
  189. function eachLocation(cb: LocationCallback) {
  190. visuals.forEach(({ visual }) => {
  191. visual.eachLocation(cb);
  192. });
  193. }
  194. function getAllLoci() {
  195. return [Structure.Loci(_structure.target)];
  196. }
  197. function mark(loci: Loci, action: MarkerAction) {
  198. if (!_structure) return false;
  199. if (!MarkerActions.is(_state.markerActions, action)) return false;
  200. if (Structure.isLoci(loci) || StructureElement.Loci.is(loci) || Bond.isLoci(loci)) {
  201. if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
  202. // Remap `loci` from equivalent structure to the current `_structure`
  203. loci = Loci.remap(loci, _structure);
  204. if (Structure.isLoci(loci) || (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci))) {
  205. // Change to `EveryLoci` to allow for downstream optimizations
  206. loci = EveryLoci;
  207. }
  208. } else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
  209. return false;
  210. }
  211. if (Loci.isEmpty(loci)) return false;
  212. let changed = false;
  213. visuals.forEach(({ visual }) => {
  214. changed = visual.mark(loci, action) || changed;
  215. });
  216. return changed;
  217. }
  218. function setVisualState(visual: UnitsVisual<P>, group: Unit.SymmetryGroup, state: Partial<StructureRepresentationState>) {
  219. const { visible, alphaFactor, pickable, overpaint, transparency, substance, clipping, themeStrength, transform, unitTransforms } = state;
  220. if (visible !== undefined) visual.setVisibility(visible);
  221. if (alphaFactor !== undefined) visual.setAlphaFactor(alphaFactor);
  222. if (pickable !== undefined) visual.setPickable(pickable);
  223. if (overpaint !== undefined) visual.setOverpaint(overpaint, webgl);
  224. if (transparency !== undefined) visual.setTransparency(transparency, webgl);
  225. if (substance !== undefined) visual.setSubstance(substance, webgl);
  226. if (clipping !== undefined) visual.setClipping(clipping);
  227. if (themeStrength !== undefined) visual.setThemeStrength(themeStrength);
  228. if (transform !== undefined) {
  229. if (transform !== _state.transform || !Mat4.areEqual(transform, _state.transform, EPSILON)) {
  230. visual.setTransform(transform);
  231. }
  232. }
  233. if (unitTransforms !== undefined) {
  234. if (unitTransforms) {
  235. // console.log(group.hashCode, unitTransforms.getSymmetryGroupTransforms(group))
  236. visual.setTransform(undefined, unitTransforms.getSymmetryGroupTransforms(group));
  237. } else if (unitTransforms !== _state.unitTransforms) {
  238. visual.setTransform(undefined, null);
  239. }
  240. }
  241. }
  242. function setState(state: Partial<StructureRepresentationState>) {
  243. const { visible, alphaFactor, pickable, overpaint, transparency, substance, clipping, themeStrength, transform, unitTransforms, syncManually, markerActions } = state;
  244. const newState: Partial<StructureRepresentationState> = {};
  245. if (visible !== _state.visible) newState.visible = visible;
  246. if (alphaFactor !== _state.alphaFactor) newState.alphaFactor = alphaFactor;
  247. if (pickable !== _state.pickable) newState.pickable = pickable;
  248. if (overpaint !== undefined && _structure) {
  249. newState.overpaint = Overpaint.remap(overpaint, _structure);
  250. }
  251. if (transparency !== undefined && _structure) {
  252. newState.transparency = Transparency.remap(transparency, _structure);
  253. }
  254. if (substance !== undefined && _structure) {
  255. newState.substance = Substance.remap(substance, _structure);
  256. }
  257. if (clipping !== undefined && _structure) {
  258. newState.clipping = Clipping.remap(clipping, _structure);
  259. }
  260. if (themeStrength !== undefined) newState.themeStrength = themeStrength;
  261. if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) {
  262. newState.transform = transform;
  263. }
  264. if (unitTransforms !== _state.unitTransforms || unitTransforms?.version !== state.unitTransformsVersion) {
  265. newState.unitTransforms = unitTransforms;
  266. _state.unitTransformsVersion = unitTransforms ? unitTransforms?.version : -1;
  267. }
  268. if (syncManually !== _state.syncManually) newState.syncManually = syncManually;
  269. if (markerActions !== _state.markerActions) newState.markerActions = markerActions;
  270. visuals.forEach(({ visual, group }) => setVisualState(visual, group, newState));
  271. StructureRepresentationStateBuilder.update(_state, newState);
  272. }
  273. function setTheme(theme: Theme) {
  274. _theme = theme;
  275. }
  276. function destroy() {
  277. visuals.forEach(({ visual }) => visual.destroy());
  278. visuals.clear();
  279. }
  280. return {
  281. label,
  282. get groupCount() {
  283. let groupCount = 0;
  284. visuals.forEach(({ visual }) => {
  285. if (visual.renderObject) groupCount += visual.groupCount;
  286. });
  287. return groupCount;
  288. },
  289. get geometryVersion() { return geometryState.version; },
  290. get props() { return _props; },
  291. get params() { return _params; },
  292. get state() { return _state; },
  293. get theme() { return _theme; },
  294. renderObjects,
  295. updated,
  296. createOrUpdate,
  297. setState,
  298. setTheme,
  299. getLoci,
  300. getAllLoci,
  301. eachLocation,
  302. mark,
  303. destroy
  304. };
  305. }