representation.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
  7. import { Representation } from '../representation';
  8. import { Shape, ShapeGroup } from '../../mol-model/shape';
  9. import { Subject } from 'rxjs';
  10. import { getNextMaterialId, createRenderObject, GraphicsRenderObject } from '../../mol-gl/render-object';
  11. import { Theme } from '../../mol-theme/theme';
  12. import { LocationIterator } from '../../mol-geo/util/location-iterator';
  13. import { VisualUpdateState } from '../util';
  14. import { createMarkers } from '../../mol-geo/geometry/marker-data';
  15. import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
  16. import { ValueCell } from '../../mol-util';
  17. import { createColors } from '../../mol-geo/geometry/color-data';
  18. import { createSizes, SizeData } from '../../mol-geo/geometry/size-data';
  19. import { Loci, isEveryLoci, EmptyLoci } from '../../mol-model/loci';
  20. import { Interval, OrderedSet } from '../../mol-data/int';
  21. import { PickingId } from '../../mol-geo/geometry/picking';
  22. import { Visual } from '../visual';
  23. import { RuntimeContext, Task } from '../../mol-task';
  24. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  25. import { isDebugMode } from '../../mol-util/debug';
  26. export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { }
  27. export type ShapeGetter<D, G extends Geometry, P extends Geometry.Params<G>> = (ctx: RuntimeContext, data: D, props: PD.Values<P>, shape?: Shape<G>) => Shape<G> | Promise<Shape<G>>
  28. export interface ShapeBuilder<G extends Geometry, P extends Geometry.Params<G>> {
  29. /** Hook to modify representation props */
  30. modifyProps?: (props: Partial<PD.Values<P>>) => Partial<PD.Values<P>>
  31. /** Hook to modify representation state */
  32. modifyState?: (state: Partial<Representation.State>) => Partial<Representation.State>
  33. }
  34. export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>>(getShape: ShapeGetter<D, G, P>, geometryUtils: GeometryUtils<G>, builder: ShapeBuilder<G, P> = {}): ShapeRepresentation<D, G, P> {
  35. let version = 0;
  36. const updated = new Subject<number>();
  37. const _state = Representation.createState();
  38. const materialId = getNextMaterialId();
  39. const renderObjects: GraphicsRenderObject<G['kind']>[] = [];
  40. let _renderObject: GraphicsRenderObject<G['kind']> | undefined;
  41. let _shape: Shape<G>;
  42. let geometryVersion = -1;
  43. const _theme = Theme.createEmpty();
  44. let currentProps: PD.Values<P> = PD.getDefaultValues(geometryUtils.Params as P); // TODO avoid casting
  45. let currentParams: P;
  46. let locationIt: LocationIterator;
  47. let positionIt: LocationIterator;
  48. if (builder.modifyState) Representation.updateState(_state, builder.modifyState(_state));
  49. const updateState = VisualUpdateState.create();
  50. function prepareUpdate(props: Partial<PD.Values<P>> = {}, shape?: Shape<G>) {
  51. VisualUpdateState.reset(updateState);
  52. if (!shape && !_shape) {
  53. // console.error('no shape given')
  54. return;
  55. } else if (shape && !_shape) {
  56. // console.log('first shape')
  57. updateState.createNew = true;
  58. } else if (shape && _shape && shape.id === _shape.id) {
  59. // console.log('same shape')
  60. // nothing to set
  61. } else if (shape && _shape && shape.id !== _shape.id) {
  62. // console.log('new shape')
  63. updateState.updateTransform = true;
  64. updateState.createGeometry = true;
  65. } else if (!shape) {
  66. // console.log('only props')
  67. // nothing to set
  68. } else {
  69. console.warn('unexpected state');
  70. }
  71. if (updateState.updateTransform) {
  72. updateState.updateColor = true;
  73. updateState.updateSize = true;
  74. }
  75. if (updateState.createGeometry) {
  76. updateState.updateColor = true;
  77. updateState.updateSize = true;
  78. }
  79. }
  80. function createOrUpdate(props: Partial<PD.Values<P>> = {}, data?: D) {
  81. if (builder.modifyProps) props = builder.modifyProps(props);
  82. return Task.create('ShapeRepresentation.create', async runtime => {
  83. const newProps = Object.assign(currentProps, props);
  84. const shape = data ? await getShape(runtime, data, newProps, _shape) : undefined;
  85. prepareUpdate(props, shape);
  86. if (shape) {
  87. _shape = shape;
  88. Object.assign(_theme, Shape.getTheme(_shape));
  89. }
  90. if (updateState.createNew) {
  91. renderObjects.length = 0; // clear list o renderObjects
  92. locationIt = Shape.groupIterator(_shape);
  93. const transform = Shape.createTransform(_shape.transforms);
  94. const values = geometryUtils.createValues(_shape.geometry, transform, locationIt, _theme, newProps);
  95. const state = geometryUtils.createRenderableState(newProps);
  96. if (builder.modifyState) Object.assign(state, builder.modifyState(state));
  97. Representation.updateState(_state, state);
  98. _renderObject = createRenderObject(_shape.geometry.kind, values, state, materialId);
  99. if (_renderObject) renderObjects.push(_renderObject); // add new renderObject to list
  100. positionIt = geometryUtils.createPositionIterator(_shape.geometry, _renderObject.values);
  101. } else {
  102. if (!_renderObject) {
  103. throw new Error('expected renderObject to be available');
  104. }
  105. if (updateState.updateTransform) {
  106. // console.log('update transform')
  107. Shape.createTransform(_shape.transforms, _renderObject.values);
  108. locationIt = Shape.groupIterator(_shape);
  109. const { instanceCount, groupCount } = locationIt;
  110. createMarkers(instanceCount * groupCount, _renderObject.values);
  111. }
  112. if (updateState.createGeometry) {
  113. // console.log('update geometry')
  114. ValueCell.updateIfChanged(_renderObject.values.drawCount, Geometry.getDrawCount(_shape.geometry));
  115. ValueCell.updateIfChanged(_renderObject.values.uVertexCount, Geometry.getVertexCount(_shape.geometry));
  116. }
  117. if (updateState.updateTransform || updateState.createGeometry) {
  118. // console.log('updateBoundingSphere')
  119. geometryUtils.updateBoundingSphere(_renderObject.values, _shape.geometry);
  120. positionIt = geometryUtils.createPositionIterator(_shape.geometry, _renderObject.values);
  121. }
  122. if (updateState.updateColor) {
  123. // console.log('update color')
  124. createColors(locationIt, positionIt, _theme.color, _renderObject.values);
  125. }
  126. if (updateState.updateSize) {
  127. // not all geometries have size data, so check here
  128. if ('uSize' in _renderObject.values) {
  129. // console.log('update size')
  130. createSizes(locationIt, _theme.size, _renderObject.values as SizeData);
  131. }
  132. }
  133. geometryUtils.updateValues(_renderObject.values, newProps);
  134. geometryUtils.updateRenderableState(_renderObject.state, newProps);
  135. }
  136. currentProps = newProps;
  137. if (updateState.createGeometry || updateState.createNew) {
  138. geometryVersion += 1;
  139. }
  140. // increment version
  141. updated.next(version++);
  142. });
  143. }
  144. function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
  145. if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
  146. return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length));
  147. } else {
  148. return eachShapeGroup(loci, _shape, apply);
  149. }
  150. }
  151. return {
  152. label: 'Shape geometry',
  153. get groupCount() { return locationIt ? locationIt.count : 0; },
  154. get props() { return currentProps; },
  155. get params() { return currentParams; },
  156. get state() { return _state; },
  157. get theme() { return _theme; },
  158. renderObjects,
  159. get geometryVersion() { return geometryVersion; },
  160. updated,
  161. createOrUpdate,
  162. getLoci(pickingId?: PickingId) {
  163. if (pickingId === undefined) return Shape.Loci(_shape);
  164. const { objectId, groupId, instanceId } = pickingId;
  165. if (_renderObject && _renderObject.id === objectId) {
  166. return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId), instance: instanceId }]);
  167. }
  168. return EmptyLoci;
  169. },
  170. mark(loci: Loci, action: MarkerAction) {
  171. if (!MarkerActions.is(_state.markerActions, action)) return false;
  172. if (ShapeGroup.isLoci(loci) || Shape.isLoci(loci)) {
  173. if (loci.shape !== _shape) return false;
  174. } else if (!isEveryLoci(loci)) {
  175. return false;
  176. }
  177. return Visual.mark(_renderObject, loci, action, lociApply);
  178. },
  179. setState(state: Partial<Representation.State>) {
  180. if (builder.modifyState) state = builder.modifyState(state);
  181. if (_renderObject) {
  182. if (state.visible !== undefined) Visual.setVisibility(_renderObject, state.visible);
  183. if (state.alphaFactor !== undefined) Visual.setAlphaFactor(_renderObject, state.alphaFactor);
  184. if (state.pickable !== undefined) Visual.setPickable(_renderObject, state.pickable);
  185. if (state.colorOnly !== undefined) Visual.setColorOnly(_renderObject, state.colorOnly);
  186. if (state.overpaint !== undefined) {
  187. Visual.setOverpaint(_renderObject, state.overpaint, lociApply, true);
  188. }
  189. if (state.transparency !== undefined) {
  190. Visual.setTransparency(_renderObject, state.transparency, lociApply, true);
  191. }
  192. if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform);
  193. }
  194. Representation.updateState(_state, state);
  195. },
  196. setTheme(theme: Theme) {
  197. if (isDebugMode) {
  198. console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
  199. }
  200. },
  201. destroy() {
  202. renderObjects.length = 0;
  203. if (_renderObject) {
  204. _renderObject.state.disposed = true;
  205. _renderObject = undefined;
  206. }
  207. }
  208. };
  209. }
  210. function eachShapeGroup(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) {
  211. if (!ShapeGroup.isLoci(loci)) return false;
  212. if (loci.shape !== shape) return false;
  213. let changed = false;
  214. const { groupCount } = shape;
  215. const { groups } = loci;
  216. for (const { ids, instance } of groups) {
  217. if (Interval.is(ids)) {
  218. const start = instance * groupCount + Interval.start(ids);
  219. const end = instance * groupCount + Interval.end(ids);
  220. if (apply(Interval.ofBounds(start, end))) changed = true;
  221. } else {
  222. for (let i = 0, _i = ids.length; i < _i; i++) {
  223. const idx = instance * groupCount + ids[i];
  224. if (apply(Interval.ofSingleton(idx))) changed = true;
  225. }
  226. }
  227. }
  228. return changed;
  229. }