representation.ts 10 KB

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