representation.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. */
  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 { LocationCallback, 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 (props.instanceGranularity !== currentProps.instanceGranularity) {
  72. updateState.updateTransform = true;
  73. }
  74. if (updateState.updateTransform) {
  75. updateState.updateColor = true;
  76. updateState.updateSize = true;
  77. }
  78. if (updateState.createGeometry) {
  79. updateState.updateColor = true;
  80. updateState.updateSize = true;
  81. }
  82. }
  83. function createOrUpdate(props: Partial<PD.Values<P>> = {}, data?: D) {
  84. if (builder.modifyProps) props = builder.modifyProps(props);
  85. return Task.create('ShapeRepresentation.create', async runtime => {
  86. const newProps = Object.assign(currentProps, props);
  87. const shape = data ? await getShape(runtime, data, newProps, _shape) : undefined;
  88. prepareUpdate(props, shape);
  89. if (shape) {
  90. _shape = shape;
  91. Object.assign(_theme, Shape.getTheme(_shape));
  92. }
  93. if (updateState.createNew) {
  94. renderObjects.length = 0; // clear list o renderObjects
  95. locationIt = Shape.groupIterator(_shape);
  96. const transform = Shape.createTransform(_shape.transforms);
  97. const values = geometryUtils.createValues(_shape.geometry, transform, locationIt, _theme, newProps);
  98. const state = geometryUtils.createRenderableState(newProps);
  99. if (builder.modifyState) Object.assign(state, builder.modifyState(state));
  100. Representation.updateState(_state, state);
  101. _renderObject = createRenderObject(_shape.geometry.kind, values, state, materialId);
  102. if (_renderObject) renderObjects.push(_renderObject); // add new renderObject to list
  103. positionIt = geometryUtils.createPositionIterator(_shape.geometry, _renderObject.values);
  104. } else {
  105. if (!_renderObject) {
  106. throw new Error('expected renderObject to be available');
  107. }
  108. if (updateState.updateTransform) {
  109. // console.log('update transform')
  110. Shape.createTransform(_shape.transforms, _renderObject.values);
  111. locationIt = Shape.groupIterator(_shape);
  112. const { instanceCount, groupCount } = locationIt;
  113. if (props.instanceGranularity) {
  114. createMarkers(instanceCount, 'instance', _renderObject.values);
  115. } else {
  116. createMarkers(instanceCount * groupCount, 'groupInstance', _renderObject.values);
  117. }
  118. }
  119. if (updateState.createGeometry) {
  120. // console.log('update geometry')
  121. ValueCell.updateIfChanged(_renderObject.values.drawCount, Geometry.getDrawCount(_shape.geometry));
  122. ValueCell.updateIfChanged(_renderObject.values.uVertexCount, Geometry.getVertexCount(_shape.geometry));
  123. ValueCell.updateIfChanged(_renderObject.values.uGroupCount, Geometry.getGroupCount(_shape.geometry));
  124. }
  125. if (updateState.updateTransform || updateState.createGeometry) {
  126. // console.log('updateBoundingSphere')
  127. geometryUtils.updateBoundingSphere(_renderObject.values, _shape.geometry);
  128. positionIt = geometryUtils.createPositionIterator(_shape.geometry, _renderObject.values);
  129. }
  130. if (updateState.updateColor) {
  131. // console.log('update color')
  132. createColors(locationIt, positionIt, _theme.color, _renderObject.values);
  133. }
  134. if (updateState.updateSize) {
  135. // not all geometries have size data, so check here
  136. if ('uSize' in _renderObject.values) {
  137. // console.log('update size')
  138. createSizes(locationIt, _theme.size, _renderObject.values as SizeData);
  139. }
  140. }
  141. geometryUtils.updateValues(_renderObject.values, newProps);
  142. geometryUtils.updateRenderableState(_renderObject.state, newProps);
  143. }
  144. currentProps = newProps;
  145. if (updateState.createGeometry || updateState.createNew) {
  146. geometryVersion += 1;
  147. }
  148. // increment version
  149. updated.next(version++);
  150. });
  151. }
  152. function eachInstance(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) {
  153. let changed = false;
  154. if (!ShapeGroup.isLoci(loci)) return false;
  155. if (ShapeGroup.isLociEmpty(loci)) return false;
  156. if (loci.shape !== shape) return false;
  157. for (const g of loci.groups) {
  158. if (apply(Interval.ofSingleton(g.instance))) changed = true;
  159. }
  160. return changed;
  161. }
  162. function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
  163. if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
  164. if (currentProps.instanceGranularity) {
  165. return apply(Interval.ofBounds(0, _shape.transforms.length));
  166. } else {
  167. return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length));
  168. }
  169. } else {
  170. if (currentProps.instanceGranularity) {
  171. return eachInstance(loci, _shape, apply);
  172. } else {
  173. return eachShapeGroup(loci, _shape, apply);
  174. }
  175. }
  176. }
  177. return {
  178. label: 'Shape geometry',
  179. get groupCount() { return locationIt ? locationIt.count : 0; },
  180. get props() { return currentProps; },
  181. get params() { return currentParams; },
  182. get state() { return _state; },
  183. get theme() { return _theme; },
  184. renderObjects,
  185. get geometryVersion() { return geometryVersion; },
  186. updated,
  187. createOrUpdate,
  188. getLoci(pickingId: PickingId) {
  189. const { objectId, groupId, instanceId } = pickingId;
  190. if (_renderObject && _renderObject.id === objectId) {
  191. return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId), instance: instanceId }]);
  192. }
  193. return EmptyLoci;
  194. },
  195. getAllLoci() {
  196. return [Shape.Loci(_shape)];
  197. },
  198. eachLocation: (cb: LocationCallback) => {
  199. locationIt.reset();
  200. while (locationIt.hasNext) {
  201. const { location, isSecondary } = locationIt.move();
  202. cb(location, isSecondary);
  203. }
  204. },
  205. mark(loci: Loci, action: MarkerAction) {
  206. if (!MarkerActions.is(_state.markerActions, action)) return false;
  207. if (ShapeGroup.isLoci(loci) || Shape.isLoci(loci)) {
  208. if (loci.shape !== _shape) return false;
  209. } else if (!isEveryLoci(loci)) {
  210. return false;
  211. }
  212. return Visual.mark(_renderObject, loci, action, lociApply);
  213. },
  214. setState(state: Partial<Representation.State>) {
  215. if (builder.modifyState) state = builder.modifyState(state);
  216. if (_renderObject) {
  217. if (state.visible !== undefined) Visual.setVisibility(_renderObject, state.visible);
  218. if (state.alphaFactor !== undefined) Visual.setAlphaFactor(_renderObject, state.alphaFactor);
  219. if (state.pickable !== undefined) Visual.setPickable(_renderObject, state.pickable);
  220. if (state.colorOnly !== undefined) Visual.setColorOnly(_renderObject, state.colorOnly);
  221. if (state.overpaint !== undefined) {
  222. Visual.setOverpaint(_renderObject, state.overpaint, lociApply, true);
  223. }
  224. if (state.transparency !== undefined) {
  225. Visual.setTransparency(_renderObject, state.transparency, lociApply, true);
  226. }
  227. if (state.substance !== undefined) {
  228. Visual.setSubstance(_renderObject, state.substance, lociApply, true);
  229. }
  230. if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform);
  231. }
  232. Representation.updateState(_state, state);
  233. },
  234. setTheme(theme: Theme) {
  235. if (isDebugMode) {
  236. console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
  237. }
  238. },
  239. destroy() {
  240. renderObjects.length = 0;
  241. if (_renderObject) {
  242. _renderObject.state.disposed = true;
  243. _renderObject = undefined;
  244. }
  245. }
  246. };
  247. }
  248. function eachShapeGroup(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) {
  249. if (!ShapeGroup.isLoci(loci)) return false;
  250. if (loci.shape !== shape) return false;
  251. let changed = false;
  252. const { groupCount } = shape;
  253. const { groups } = loci;
  254. for (const { ids, instance } of groups) {
  255. if (Interval.is(ids)) {
  256. const start = instance * groupCount + Interval.start(ids);
  257. const end = instance * groupCount + Interval.end(ids);
  258. if (apply(Interval.ofBounds(start, end))) changed = true;
  259. } else {
  260. for (let i = 0, _i = ids.length; i < _i; i++) {
  261. const idx = instance * groupCount + ids[i];
  262. if (apply(Interval.ofSingleton(idx))) changed = true;
  263. }
  264. }
  265. }
  266. return changed;
  267. }