representation.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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 { ParamDefinition as PD } from '../mol-util/param-definition';
  7. import { WebGLContext } from '../mol-gl/webgl/context';
  8. import { ColorTheme } from '../mol-theme/color';
  9. import { SizeTheme } from '../mol-theme/size';
  10. import { ThemeRegistryContext, Theme } from '../mol-theme/theme';
  11. import { Subject } from 'rxjs';
  12. import { GraphicsRenderObject } from '../mol-gl/render-object';
  13. import { Task } from '../mol-task';
  14. import { PickingId } from '../mol-geo/geometry/picking';
  15. import { MarkerAction } from '../mol-util/marker-action';
  16. import { Loci as ModelLoci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
  17. import { Overpaint } from '../mol-theme/overpaint';
  18. import { Transparency } from '../mol-theme/transparency';
  19. import { Mat4 } from '../mol-math/linear-algebra';
  20. import { getQualityProps } from './util';
  21. import { BaseGeometry } from '../mol-geo/geometry/base';
  22. import { Visual } from './visual';
  23. import { CustomProperty } from '../mol-model-props/common/custom-property';
  24. // export interface RepresentationProps {
  25. // visuals?: string[]
  26. // }
  27. export type RepresentationProps = { [k: string]: any }
  28. export interface RepresentationContext {
  29. readonly webgl?: WebGLContext
  30. readonly colorThemeRegistry: ColorTheme.Registry
  31. readonly sizeThemeRegistry: SizeTheme.Registry
  32. }
  33. export type RepresentationParamsGetter<D, P extends PD.Params> = (ctx: ThemeRegistryContext, data: D) => P
  34. export type RepresentationFactory<D, P extends PD.Params, S extends Representation.State> = (ctx: RepresentationContext, getParams: RepresentationParamsGetter<D, P>) => Representation<D, P, S>
  35. //
  36. export interface RepresentationProvider<D, P extends PD.Params, S extends Representation.State> {
  37. readonly label: string
  38. readonly description: string
  39. readonly factory: RepresentationFactory<D, P, S>
  40. readonly getParams: RepresentationParamsGetter<D, P>
  41. readonly defaultValues: PD.Values<P>
  42. readonly defaultColorTheme: string
  43. readonly defaultSizeTheme: string
  44. readonly isApplicable: (data: D) => boolean
  45. readonly ensureCustomProperties?: (ctx: CustomProperty.Context, data: D) => Promise<void>
  46. }
  47. export namespace RepresentationProvider {
  48. export type ParamValues<R extends RepresentationProvider<any, any, any>> = R extends RepresentationProvider<any, infer P, any> ? PD.Values<P> : never;
  49. export function getDetaultParams<R extends RepresentationProvider<D, any, any>, D>(r: R, ctx: ThemeRegistryContext, data: D) {
  50. return PD.getDefaultValues(r.getParams(ctx, data));
  51. }
  52. }
  53. export type AnyRepresentationProvider = RepresentationProvider<any, {}, Representation.State>
  54. export const EmptyRepresentationProvider = {
  55. label: '',
  56. description: '',
  57. factory: () => Representation.Empty,
  58. getParams: () => ({}),
  59. defaultValues: {}
  60. }
  61. function getTypes(list: { name: string, provider: RepresentationProvider<any, any, any> }[]) {
  62. return list.map(e => [e.name, e.provider.label] as [string, string]);
  63. }
  64. export class RepresentationRegistry<D, S extends Representation.State> {
  65. private _list: { name: string, provider: RepresentationProvider<D, any, any> }[] = []
  66. private _map = new Map<string, RepresentationProvider<D, any, any>>()
  67. private _name = new Map<RepresentationProvider<D, any, any>, string>()
  68. get default() { return this._list[0]; }
  69. get types(): [string, string][] { return getTypes(this._list) }
  70. constructor() {};
  71. add<P extends PD.Params>(name: string, provider: RepresentationProvider<D, P, S>) {
  72. this._list.push({ name, provider })
  73. this._map.set(name, provider)
  74. this._name.set(provider, name)
  75. }
  76. getName(provider: RepresentationProvider<D, any, S>): string {
  77. if (!this._name.has(provider)) throw new Error(`'${provider.label}' is not a registered represenatation provider.`);
  78. return this._name.get(provider)!;
  79. }
  80. remove(name: string) {
  81. this._list.splice(this._list.findIndex(e => e.name === name), 1)
  82. const p = this._map.get(name);
  83. if (p) {
  84. this._map.delete(name);
  85. this._name.delete(p);
  86. }
  87. }
  88. get<P extends PD.Params>(name: string): RepresentationProvider<D, P, S> {
  89. return this._map.get(name) || EmptyRepresentationProvider as unknown as RepresentationProvider<D, P, S>
  90. }
  91. get list() {
  92. return this._list
  93. }
  94. getApplicableList(data: D) {
  95. return this._list.filter(e => e.provider.isApplicable(data));
  96. }
  97. getApplicableTypes(data: D) {
  98. return getTypes(this.getApplicableList(data))
  99. }
  100. }
  101. //
  102. export { Representation }
  103. interface Representation<D, P extends PD.Params = {}, S extends Representation.State = Representation.State> {
  104. readonly label: string
  105. readonly updated: Subject<number>
  106. /** Number of addressable groups in all visuals of the representation */
  107. readonly groupCount: number
  108. readonly renderObjects: ReadonlyArray<GraphicsRenderObject>
  109. readonly props: Readonly<PD.Values<P>>
  110. readonly params: Readonly<P>
  111. readonly state: Readonly<S>
  112. readonly theme: Readonly<Theme>
  113. createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void>
  114. setState: (state: Partial<S>) => void
  115. setTheme: (theme: Theme) => void
  116. /** If no pickingId is given, returns a Loci for the whole representation */
  117. getLoci: (pickingId?: PickingId) => ModelLoci
  118. mark: (loci: ModelLoci, action: MarkerAction) => boolean
  119. destroy: () => void
  120. }
  121. namespace Representation {
  122. export interface Loci<T extends ModelLoci = ModelLoci> { loci: T, repr?: Representation.Any }
  123. export namespace Loci {
  124. export function areEqual(a: Loci, b: Loci) {
  125. return a.repr === b.repr && ModelLoci.areEqual(a.loci, b.loci);
  126. }
  127. export const Empty: Loci = { loci: EmptyLoci };
  128. }
  129. export interface State {
  130. /** Controls if the representation's renderobjects are rendered or not */
  131. visible: boolean
  132. /** A factor applied to alpha value of the representation's renderobjects */
  133. alphaFactor: number
  134. /** Controls if the representation's renderobjects are pickable or not */
  135. pickable: boolean
  136. /** Overpaint applied to the representation's renderobjects */
  137. overpaint: Overpaint
  138. /** Per group transparency applied to the representation's renderobjects */
  139. transparency: Transparency
  140. /** Controls if the representation's renderobjects are synced automatically with GPU or not */
  141. syncManually: boolean
  142. /** A transformation applied to the representation's renderobjects */
  143. transform: Mat4
  144. }
  145. export function createState(): State {
  146. return { visible: false, alphaFactor: 0, pickable: false, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty }
  147. }
  148. export function updateState(state: State, update: Partial<State>) {
  149. if (update.visible !== undefined) state.visible = update.visible
  150. if (update.alphaFactor !== undefined) state.alphaFactor = update.alphaFactor
  151. if (update.pickable !== undefined) state.pickable = update.pickable
  152. if (update.overpaint !== undefined) state.overpaint = update.overpaint
  153. if (update.transparency !== undefined) state.transparency = update.transparency
  154. if (update.syncManually !== undefined) state.syncManually = update.syncManually
  155. if (update.transform !== undefined) Mat4.copy(state.transform, update.transform)
  156. }
  157. export interface StateBuilder<S extends State> {
  158. create(): S
  159. update(state: S, update: Partial<S>): void
  160. }
  161. export const StateBuilder: StateBuilder<State> = { create: createState, update: updateState }
  162. export type Any = Representation<any, any, any>
  163. export const Empty: Any = {
  164. label: '', groupCount: 0, renderObjects: [], props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
  165. createOrUpdate: () => Task.constant('', undefined),
  166. setState: () => {},
  167. setTheme: () => {},
  168. getLoci: () => EmptyLoci,
  169. mark: () => false,
  170. destroy: () => {}
  171. }
  172. export type Def<D, P extends PD.Params = {}, S extends State = State> = { [k: string]: RepresentationFactory<D, P, S> }
  173. export function createMulti<D, P extends PD.Params = {}, S extends State = State>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<D, P>, stateBuilder: StateBuilder<S>, reprDefs: Def<D, P>): Representation<D, P, S> {
  174. let version = 0
  175. const updated = new Subject<number>()
  176. const currentState = stateBuilder.create()
  177. let currentTheme = Theme.createEmpty()
  178. let currentParams: P
  179. let currentProps: PD.Values<P>
  180. let currentData: D
  181. const reprMap: { [k: number]: string } = {}
  182. const reprList: Representation<D, P>[] = Object.keys(reprDefs).map((name, i) => {
  183. reprMap[i] = name
  184. return reprDefs[name](ctx, getParams)
  185. })
  186. return {
  187. label,
  188. updated,
  189. get groupCount() {
  190. let groupCount = 0
  191. if (currentProps) {
  192. const { visuals } = currentProps
  193. for (let i = 0, il = reprList.length; i < il; ++i) {
  194. if (!visuals || visuals.includes(reprMap[i])) {
  195. groupCount += reprList[i].groupCount
  196. }
  197. }
  198. }
  199. return groupCount
  200. },
  201. get renderObjects() {
  202. const renderObjects: GraphicsRenderObject[] = []
  203. if (currentProps) {
  204. const { visuals } = currentProps
  205. for (let i = 0, il = reprList.length; i < il; ++i) {
  206. if (!visuals || visuals.includes(reprMap[i])) {
  207. renderObjects.push(...reprList[i].renderObjects)
  208. }
  209. }
  210. }
  211. return renderObjects
  212. },
  213. get props() { return currentProps },
  214. get params() { return currentParams },
  215. createOrUpdate: (props: Partial<P> = {}, data?: D) => {
  216. if (data && data !== currentData) {
  217. currentParams = getParams(ctx, data)
  218. currentData = data
  219. if (!currentProps) currentProps = PD.getDefaultValues(currentParams) as P
  220. }
  221. const qualityProps = getQualityProps(Object.assign({}, currentProps, props), currentData)
  222. Object.assign(currentProps, props, qualityProps)
  223. const { visuals } = currentProps
  224. return Task.create(`Creating or updating '${label}' representation`, async runtime => {
  225. for (let i = 0, il = reprList.length; i < il; ++i) {
  226. if (!visuals || visuals.includes(reprMap[i])) {
  227. await reprList[i].createOrUpdate(currentProps, currentData).runInContext(runtime)
  228. }
  229. }
  230. updated.next(version++)
  231. })
  232. },
  233. get state() { return currentState },
  234. get theme() { return currentTheme },
  235. getLoci: (pickingId?: PickingId) => {
  236. if (pickingId === undefined) {
  237. return reprList.length > 0 ? reprList[0].getLoci() : EmptyLoci
  238. }
  239. for (let i = 0, il = reprList.length; i < il; ++i) {
  240. const loci = reprList[i].getLoci(pickingId)
  241. if (!isEmptyLoci(loci)) return loci
  242. }
  243. return EmptyLoci
  244. },
  245. mark: (loci: ModelLoci, action: MarkerAction) => {
  246. let marked = false
  247. for (let i = 0, il = reprList.length; i < il; ++i) {
  248. marked = reprList[i].mark(loci, action) || marked
  249. }
  250. return marked
  251. },
  252. setState: (state: Partial<S>) => {
  253. for (let i = 0, il = reprList.length; i < il; ++i) {
  254. reprList[i].setState(state)
  255. }
  256. stateBuilder.update(currentState, state)
  257. },
  258. setTheme: (theme: Theme) => {
  259. for (let i = 0, il = reprList.length; i < il; ++i) {
  260. reprList[i].setTheme(theme)
  261. }
  262. },
  263. destroy() {
  264. for (let i = 0, il = reprList.length; i < il; ++i) {
  265. reprList[i].destroy()
  266. }
  267. }
  268. }
  269. }
  270. export function fromRenderObject(label: string, renderObject: GraphicsRenderObject): Representation<GraphicsRenderObject, BaseGeometry.Params> {
  271. let version = 0
  272. const updated = new Subject<number>()
  273. const currentState = Representation.createState()
  274. const currentTheme = Theme.createEmpty()
  275. const currentParams = PD.clone(BaseGeometry.Params)
  276. const currentProps = PD.getDefaultValues(BaseGeometry.Params)
  277. return {
  278. label,
  279. updated,
  280. get groupCount() { return renderObject.values.uGroupCount.ref.value },
  281. get renderObjects() { return [renderObject] },
  282. get props() { return currentProps },
  283. get params() { return currentParams },
  284. createOrUpdate: (props: Partial<PD.Values<BaseGeometry.Params>> = {}) => {
  285. const qualityProps = getQualityProps(Object.assign({}, currentProps, props))
  286. Object.assign(currentProps, props, qualityProps)
  287. return Task.create(`Updating '${label}' representation`, async runtime => {
  288. // TODO
  289. updated.next(version++)
  290. })
  291. },
  292. get state() { return currentState },
  293. get theme() { return currentTheme },
  294. getLoci: () => {
  295. // TODO
  296. return EmptyLoci
  297. },
  298. mark: (loci: ModelLoci, action: MarkerAction) => {
  299. // TODO
  300. return false
  301. },
  302. setState: (state: Partial<State>) => {
  303. if (state.visible !== undefined) Visual.setVisibility(renderObject, state.visible)
  304. if (state.alphaFactor !== undefined) Visual.setAlphaFactor(renderObject, state.alphaFactor)
  305. if (state.pickable !== undefined) Visual.setPickable(renderObject, state.pickable)
  306. if (state.overpaint !== undefined) {
  307. // TODO
  308. }
  309. if (state.transparency !== undefined) {
  310. // TODO
  311. }
  312. if (state.transform !== undefined) Visual.setTransform(renderObject, state.transform)
  313. Representation.updateState(currentState, state)
  314. },
  315. setTheme: () => { },
  316. destroy() { }
  317. }
  318. }
  319. }