representation.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. import { Overpaint } from 'mol-theme/overpaint';
  29. import { applyOverpaintColor } from 'mol-geo/geometry/overpaint-data';
  30. export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { }
  31. 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>>
  32. export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>>(getShape: ShapeGetter<D, G, P>, geometryUtils: GeometryUtils<G>): ShapeRepresentation<D, G, P> {
  33. let version = 0
  34. const updated = new Subject<number>()
  35. const _state = Representation.createState()
  36. const renderObjects: GraphicsRenderObject[] = []
  37. let _renderObject: GraphicsRenderObject | undefined
  38. let _shape: Shape<G>
  39. let _theme = createEmptyTheme()
  40. let currentProps: PD.Values<P> = PD.getDefaultValues(geometryUtils.Params as P) // TODO avoid casting
  41. let currentParams: P
  42. let locationIt: LocationIterator
  43. const updateState = VisualUpdateState.create()
  44. function prepareUpdate(props: Partial<PD.Values<P>> = {}, shape?: Shape<G>) {
  45. VisualUpdateState.reset(updateState)
  46. if (!shape && !_shape) {
  47. // console.error('no shape given')
  48. return
  49. } else if (shape && !_shape) {
  50. // console.log('first shape')
  51. updateState.createNew = true
  52. } else if (shape && _shape && shape.id === _shape.id) {
  53. // console.log('same shape')
  54. // trigger color update when shape has not changed
  55. updateState.updateColor = true
  56. updateState.updateTransform = true
  57. } else if (shape && _shape && shape.id !== _shape.id) {
  58. // console.log('new shape')
  59. updateState.updateTransform = true
  60. updateState.createGeometry = true
  61. } else if (!shape) {
  62. // console.log('only props')
  63. // nothing to set
  64. } else {
  65. console.warn('unexpected state')
  66. }
  67. if (updateState.updateTransform) {
  68. updateState.updateColor = true
  69. updateState.updateSize = true
  70. }
  71. if (updateState.createGeometry) {
  72. updateState.updateColor = true
  73. updateState.updateSize = true
  74. }
  75. }
  76. function createOrUpdate(props: Partial<PD.Values<P>> = {}, data?: D) {
  77. return Task.create('ShapeRepresentation.create', async runtime => {
  78. const newProps = Object.assign(currentProps, props)
  79. const shape = data ? await getShape(runtime, data, newProps, _shape) : undefined
  80. prepareUpdate(props, shape)
  81. if (shape) {
  82. _shape = shape
  83. _theme.color = ShapeGroupColorTheme({ shape: _shape }, {})
  84. _theme.size = ShapeGroupSizeTheme({ shape: _shape }, {})
  85. }
  86. if (updateState.createNew) {
  87. renderObjects.length = 0 // clear list o renderObjects
  88. locationIt = ShapeGroupIterator.fromShape(_shape)
  89. const transform = createShapeTransform(_shape.transforms)
  90. const values = geometryUtils.createValues(_shape.geometry, transform, locationIt, _theme, newProps)
  91. const state = geometryUtils.createRenderableState(newProps)
  92. _renderObject = createRenderObject(_shape.geometry.kind, values, state)
  93. if (_renderObject) renderObjects.push(_renderObject) // add new renderObject to list
  94. } else {
  95. if (!_renderObject) {
  96. throw new Error('expected renderObject to be available')
  97. }
  98. if (updateState.updateTransform) {
  99. // console.log('update transform')
  100. createShapeTransform(_shape.transforms, _renderObject.values)
  101. locationIt = ShapeGroupIterator.fromShape(_shape)
  102. const { instanceCount, groupCount } = locationIt
  103. createMarkers(instanceCount * groupCount, _renderObject.values)
  104. }
  105. if (updateState.createGeometry) {
  106. // console.log('update geometry')
  107. ValueCell.update(_renderObject.values.drawCount, Geometry.getDrawCount(_shape.geometry))
  108. }
  109. if (updateState.updateTransform || updateState.createGeometry) {
  110. // console.log('updateBoundingSphere')
  111. geometryUtils.updateBoundingSphere(_renderObject.values, _shape.geometry)
  112. }
  113. if (updateState.updateColor) {
  114. // console.log('update color')
  115. createColors(locationIt, _theme.color, _renderObject.values)
  116. }
  117. if (updateState.updateSize) {
  118. // not all geometries have size data, so check here
  119. if ('uSize' in _renderObject.values) {
  120. // console.log('update size')
  121. createSizes(locationIt, _theme.size, _renderObject.values)
  122. }
  123. }
  124. geometryUtils.updateValues(_renderObject.values, newProps)
  125. geometryUtils.updateRenderableState(_renderObject.state, newProps)
  126. }
  127. currentProps = newProps
  128. // increment version
  129. updated.next(version++)
  130. });
  131. }
  132. return {
  133. label: 'Shape geometry',
  134. get groupCount () { return locationIt ? locationIt.count : 0 },
  135. get props () { return currentProps },
  136. get params () { return currentParams },
  137. get state() { return _state },
  138. get theme() { return _theme },
  139. renderObjects,
  140. updated,
  141. createOrUpdate,
  142. getLoci(pickingId: PickingId) {
  143. const { objectId, groupId, instanceId } = pickingId
  144. if (_renderObject && _renderObject.id === objectId) {
  145. return Shape.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId) }], instanceId)
  146. }
  147. return EmptyLoci
  148. },
  149. mark(loci: Loci, action: MarkerAction) {
  150. if (!_renderObject) return false
  151. const { tMarker } = _renderObject.values
  152. const { groupCount, instanceCount } = locationIt
  153. function apply(interval: Interval) {
  154. const start = Interval.start(interval)
  155. const end = Interval.end(interval)
  156. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  157. }
  158. let changed = false
  159. if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
  160. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  161. } else {
  162. changed = eachShapeLocation(loci, _shape, apply)
  163. }
  164. if (changed) {
  165. ValueCell.update(tMarker, tMarker.ref.value)
  166. }
  167. return changed
  168. },
  169. setState(state: Partial<Representation.State>) {
  170. if (_renderObject) {
  171. if (state.visible !== undefined) Visual.setVisibility(_renderObject, state.visible)
  172. if (state.alphaFactor !== undefined) Visual.setAlphaFactor(_renderObject, state.alphaFactor)
  173. if (state.pickable !== undefined) Visual.setPickable(_renderObject, state.pickable)
  174. if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform)
  175. }
  176. Representation.updateState(_state, state)
  177. },
  178. setTheme(theme: Theme) {
  179. console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`')
  180. },
  181. setOverpaint(layers: Overpaint.Layers) {
  182. if (!_renderObject) return false
  183. const { tOverpaint } = _renderObject.values
  184. const { groupCount, instanceCount } = locationIt
  185. for (let i = 0, il = layers.length; i < il; ++i) {
  186. const { loci, color, alpha } = layers[i]
  187. const apply = (interval: Interval) => {
  188. const start = Interval.start(interval)
  189. const end = Interval.end(interval)
  190. return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, alpha)
  191. }
  192. if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
  193. apply(Interval.ofBounds(0, groupCount * instanceCount))
  194. } else {
  195. eachShapeLocation(loci, _shape, apply)
  196. }
  197. }
  198. ValueCell.update(tOverpaint, tOverpaint.ref.value)
  199. },
  200. destroy() {
  201. // TODO
  202. renderObjects.length = 0
  203. _renderObject = undefined
  204. }
  205. }
  206. }
  207. function createShapeTransform(transforms: Mat4[], transformData?: TransformData) {
  208. const transformArray = transformData && transformData.aTransform.ref.value.length >= transforms.length * 16 ? transformData.aTransform.ref.value : new Float32Array(transforms.length * 16)
  209. for (let i = 0, il = transforms.length; i < il; ++i) {
  210. Mat4.toArray(transforms[i], transformArray, i * 16)
  211. }
  212. return createTransform(transformArray, transforms.length, transformData)
  213. }
  214. function eachShapeLocation(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) {
  215. if (!Shape.isLoci(loci)) return false
  216. if (loci.shape !== shape) return false
  217. let changed = false
  218. const { groupCount } = shape
  219. const { instance, groups } = loci
  220. for (const g of groups) {
  221. if (Interval.is(g.ids)) {
  222. const start = instance * groupCount + Interval.start(g.ids)
  223. const end = instance * groupCount + Interval.end(g.ids)
  224. if (apply(Interval.ofBounds(start, end))) changed = true
  225. } else {
  226. for (let i = 0, _i = g.ids.length; i < _i; i++) {
  227. const idx = instance * groupCount + g.ids[i];
  228. if (apply(Interval.ofSingleton(idx))) changed = true
  229. }
  230. }
  231. }
  232. return changed
  233. }
  234. export namespace ShapeGroupIterator {
  235. export function fromShape(shape: Shape): LocationIterator {
  236. const instanceCount = shape.transforms.length
  237. const location = Shape.Location(shape)
  238. const getLocation = (groupIndex: number, instanceIndex: number) => {
  239. location.group = groupIndex
  240. location.instance = instanceIndex
  241. return location
  242. }
  243. return LocationIterator(shape.groupCount, instanceCount, getLocation)
  244. }
  245. }