representation.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /**
  2. * Copyright (c) 2018-2022 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, MarkerActions } 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. import { Clipping } from '../mol-theme/clipping';
  25. import { SetUtils } from '../mol-util/set';
  26. import { cantorPairing } from '../mol-data/util';
  27. import { Substance } from '../mol-theme/substance';
  28. export type RepresentationProps = { [k: string]: any }
  29. export interface RepresentationContext {
  30. readonly webgl?: WebGLContext
  31. readonly colorThemeRegistry: ColorTheme.Registry
  32. readonly sizeThemeRegistry: SizeTheme.Registry
  33. }
  34. export type RepresentationParamsGetter<D, P extends PD.Params> = (ctx: ThemeRegistryContext, data: D) => P
  35. export type RepresentationFactory<D, P extends PD.Params, S extends Representation.State> = (ctx: RepresentationContext, getParams: RepresentationParamsGetter<D, P>) => Representation<D, P, S>
  36. //
  37. export interface RepresentationProvider<D = any, P extends PD.Params = any, S extends Representation.State = any, Id extends string = string> {
  38. readonly name: Id,
  39. readonly label: string
  40. readonly description: string
  41. readonly factory: RepresentationFactory<D, P, S>
  42. readonly getParams: RepresentationParamsGetter<D, P>
  43. readonly defaultValues: PD.Values<P>
  44. readonly defaultColorTheme: { name: string, props?: {} }
  45. readonly defaultSizeTheme: { name: string, props?: {} }
  46. readonly isApplicable: (data: D) => boolean
  47. readonly ensureCustomProperties?: {
  48. attach: (ctx: CustomProperty.Context, data: D) => Promise<void>,
  49. detach: (data: D) => void
  50. }
  51. readonly getData?: (data: D, props: PD.Values<P>) => D
  52. readonly mustRecreate?: (oldProps: PD.Values<P>, newProps: PD.Values<P>) => boolean
  53. }
  54. export namespace RepresentationProvider {
  55. export type ParamValues<R extends RepresentationProvider<any, any, any>> = R extends RepresentationProvider<any, infer P, any> ? PD.Values<P> : never;
  56. export function getDetaultParams<R extends RepresentationProvider<D, any, any>, D>(r: R, ctx: ThemeRegistryContext, data: D) {
  57. return PD.getDefaultValues(r.getParams(ctx, data));
  58. }
  59. }
  60. export type AnyRepresentationProvider = RepresentationProvider<any, {}, Representation.State>
  61. export const EmptyRepresentationProvider: RepresentationProvider = {
  62. name: '',
  63. label: '',
  64. description: '',
  65. factory: () => Representation.Empty,
  66. getParams: () => ({}),
  67. defaultValues: {},
  68. defaultColorTheme: ColorTheme.EmptyProvider,
  69. defaultSizeTheme: SizeTheme.EmptyProvider,
  70. isApplicable: () => true
  71. };
  72. function getTypes(list: { name: string, provider: RepresentationProvider<any, any, any> }[]) {
  73. return list.map(e => [e.name, e.provider.label] as [string, string]);
  74. }
  75. export class RepresentationRegistry<D, S extends Representation.State> {
  76. private _list: { name: string, provider: RepresentationProvider<D, any, any> }[] = [];
  77. private _map = new Map<string, RepresentationProvider<D, any, any>>();
  78. private _name = new Map<RepresentationProvider<D, any, any>, string>();
  79. get default() { return this._list[0]; }
  80. get types(): [string, string][] { return getTypes(this._list); }
  81. constructor() {};
  82. add<P extends PD.Params>(provider: RepresentationProvider<D, P, S>) {
  83. if (this._map.has(provider.name)) {
  84. throw new Error(`${provider.name} already registered.`);
  85. }
  86. this._list.push({ name: provider.name, provider });
  87. this._map.set(provider.name, provider);
  88. this._name.set(provider, provider.name);
  89. }
  90. getName(provider: RepresentationProvider<D, any, any>): string {
  91. if (!this._name.has(provider)) throw new Error(`'${provider.label}' is not a registered represenatation provider.`);
  92. return this._name.get(provider)!;
  93. }
  94. remove(provider: RepresentationProvider<D, any, any>) {
  95. const name = provider.name;
  96. this._list.splice(this._list.findIndex(e => e.name === name), 1);
  97. const p = this._map.get(name);
  98. if (p) {
  99. this._map.delete(name);
  100. this._name.delete(p);
  101. }
  102. }
  103. get<P extends PD.Params>(name: string): RepresentationProvider<D, P, S> {
  104. return this._map.get(name) || EmptyRepresentationProvider;
  105. }
  106. get list() {
  107. return this._list;
  108. }
  109. getApplicableList(data: D) {
  110. return this._list.filter(e => e.provider.isApplicable(data));
  111. }
  112. getApplicableTypes(data: D) {
  113. return getTypes(this.getApplicableList(data));
  114. }
  115. }
  116. //
  117. export { Representation };
  118. interface Representation<D, P extends PD.Params = {}, S extends Representation.State = Representation.State> {
  119. readonly label: string
  120. readonly updated: Subject<number>
  121. /** Number of addressable groups in all visuals of the representation */
  122. readonly groupCount: number
  123. readonly renderObjects: ReadonlyArray<GraphicsRenderObject>
  124. readonly geometryVersion: number
  125. readonly props: Readonly<PD.Values<P>>
  126. readonly params: Readonly<P>
  127. readonly state: Readonly<S>
  128. readonly theme: Readonly<Theme>
  129. createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void>
  130. setState: (state: Partial<S>) => void
  131. setTheme: (theme: Theme) => void
  132. getLoci: (pickingId: PickingId) => ModelLoci
  133. getAllLoci: () => ModelLoci[]
  134. mark: (loci: ModelLoci, action: MarkerAction) => boolean
  135. destroy: () => void
  136. }
  137. namespace Representation {
  138. export interface Loci<T extends ModelLoci = ModelLoci> { loci: T, repr?: Representation.Any }
  139. export namespace Loci {
  140. export function areEqual(a: Loci, b: Loci) {
  141. return a.repr === b.repr && ModelLoci.areEqual(a.loci, b.loci);
  142. }
  143. export function isEmpty(a: Loci) {
  144. return ModelLoci.isEmpty(a.loci);
  145. }
  146. export const Empty: Loci = { loci: EmptyLoci };
  147. }
  148. export interface State {
  149. /** Controls if the representation's renderobjects are rendered or not */
  150. visible: boolean
  151. /** A factor applied to alpha value of the representation's renderobjects */
  152. alphaFactor: number
  153. /** Controls if the representation's renderobjects are pickable or not */
  154. pickable: boolean
  155. /** Controls if the representation's renderobjects is rendered in color pass (i.e., not pick and depth) or not */
  156. colorOnly: boolean
  157. /** Overpaint applied to the representation's renderobjects */
  158. overpaint: Overpaint
  159. /** Per group transparency applied to the representation's renderobjects */
  160. transparency: Transparency
  161. /** Per group material applied to the representation's renderobjects */
  162. substance: Substance
  163. /** Bit mask of per group clipping applied to the representation's renderobjects */
  164. clipping: Clipping
  165. /** Strength of the representations overpaint, transparency, substance*/
  166. themeStrength: { overpaint: number, transparency: number, substance: number }
  167. /** Controls if the representation's renderobjects are synced automatically with GPU or not */
  168. syncManually: boolean
  169. /** A transformation applied to the representation's renderobjects */
  170. transform: Mat4
  171. /** Bit mask of allowed marker actions */
  172. markerActions: MarkerActions
  173. }
  174. export function createState(): State {
  175. return {
  176. visible: true,
  177. alphaFactor: 1,
  178. pickable: true,
  179. colorOnly: false,
  180. syncManually: false,
  181. transform: Mat4.identity(),
  182. overpaint: Overpaint.Empty,
  183. transparency: Transparency.Empty,
  184. substance: Substance.Empty,
  185. clipping: Clipping.Empty,
  186. themeStrength: { overpaint: 1, transparency: 1, substance: 1 },
  187. markerActions: MarkerActions.All
  188. };
  189. }
  190. export function updateState(state: State, update: Partial<State>) {
  191. if (update.visible !== undefined) state.visible = update.visible;
  192. if (update.alphaFactor !== undefined) state.alphaFactor = update.alphaFactor;
  193. if (update.pickable !== undefined) state.pickable = update.pickable;
  194. if (update.colorOnly !== undefined) state.colorOnly = update.colorOnly;
  195. if (update.overpaint !== undefined) state.overpaint = update.overpaint;
  196. if (update.transparency !== undefined) state.transparency = update.transparency;
  197. if (update.substance !== undefined) state.substance = update.substance;
  198. if (update.clipping !== undefined) state.clipping = update.clipping;
  199. if (update.themeStrength !== undefined) state.themeStrength = update.themeStrength;
  200. if (update.syncManually !== undefined) state.syncManually = update.syncManually;
  201. if (update.transform !== undefined) Mat4.copy(state.transform, update.transform);
  202. if (update.markerActions !== undefined) state.markerActions = update.markerActions;
  203. }
  204. export interface StateBuilder<S extends State> {
  205. create(): S
  206. update(state: S, update: Partial<S>): void
  207. }
  208. export const StateBuilder: StateBuilder<State> = { create: createState, update: updateState };
  209. export type Any = Representation<any, any, any>
  210. export const Empty: Any = {
  211. label: '', groupCount: 0, renderObjects: [], geometryVersion: -1, props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
  212. createOrUpdate: () => Task.constant('', undefined),
  213. setState: () => {},
  214. setTheme: () => {},
  215. getLoci: () => EmptyLoci,
  216. getAllLoci: () => [],
  217. mark: () => false,
  218. destroy: () => {}
  219. };
  220. export type Def<D, P extends PD.Params = {}, S extends State = State> = { [k: string]: RepresentationFactory<D, P, S> }
  221. export class GeometryState {
  222. private curr = new Set<number>();
  223. private next = new Set<number>();
  224. private _version = -1;
  225. get version() {
  226. return this._version;
  227. }
  228. add(id: number, version: number) {
  229. this.next.add(cantorPairing(id, version));
  230. }
  231. snapshot() {
  232. if (!SetUtils.areEqual(this.curr, this.next)) {
  233. this._version += 1;
  234. }
  235. [this.curr, this.next] = [this.next, this.curr];
  236. this.next.clear();
  237. }
  238. }
  239. 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> {
  240. let version = 0;
  241. const updated = new Subject<number>();
  242. const geometryState = new GeometryState();
  243. const currentState = stateBuilder.create();
  244. let currentTheme = Theme.createEmpty();
  245. let currentParams: P;
  246. let currentProps: PD.Values<P>;
  247. let currentData: D;
  248. const reprMap: { [k: number]: string } = {};
  249. const reprList: Representation<D, P>[] = Object.keys(reprDefs).map((name, i) => {
  250. reprMap[i] = name;
  251. const repr = reprDefs[name](ctx, getParams);
  252. repr.setState(currentState);
  253. return repr;
  254. });
  255. return {
  256. label,
  257. updated,
  258. get groupCount() {
  259. let groupCount = 0;
  260. if (currentProps) {
  261. const { visuals } = currentProps;
  262. for (let i = 0, il = reprList.length; i < il; ++i) {
  263. if (!visuals || visuals.includes(reprMap[i])) {
  264. groupCount += reprList[i].groupCount;
  265. }
  266. }
  267. }
  268. return groupCount;
  269. },
  270. get renderObjects() {
  271. const renderObjects: GraphicsRenderObject[] = [];
  272. if (currentProps) {
  273. const { visuals } = currentProps;
  274. for (let i = 0, il = reprList.length; i < il; ++i) {
  275. if (!visuals || visuals.includes(reprMap[i])) {
  276. renderObjects.push(...reprList[i].renderObjects);
  277. }
  278. }
  279. }
  280. return renderObjects;
  281. },
  282. get geometryVersion() { return geometryState.version; },
  283. get props() { return currentProps; },
  284. get params() { return currentParams; },
  285. createOrUpdate: (props: Partial<P> = {}, data?: D) => {
  286. if (data && data !== currentData) {
  287. currentParams = getParams(ctx, data);
  288. currentData = data;
  289. if (!currentProps) currentProps = PD.getDefaultValues(currentParams) as P;
  290. }
  291. const qualityProps = getQualityProps(Object.assign({}, currentProps, props), currentData);
  292. Object.assign(currentProps, props, qualityProps);
  293. const { visuals } = currentProps;
  294. return Task.create(`Creating or updating '${label}' representation`, async runtime => {
  295. for (let i = 0, il = reprList.length; i < il; ++i) {
  296. if (!visuals || visuals.includes(reprMap[i])) {
  297. await reprList[i].createOrUpdate(currentProps, currentData).runInContext(runtime);
  298. }
  299. geometryState.add(i, reprList[i].geometryVersion);
  300. }
  301. geometryState.snapshot();
  302. updated.next(version++);
  303. });
  304. },
  305. get state() { return currentState; },
  306. get theme() { return currentTheme; },
  307. getLoci: (pickingId: PickingId) => {
  308. const { visuals } = currentProps;
  309. for (let i = 0, il = reprList.length; i < il; ++i) {
  310. if (!visuals || visuals.includes(reprMap[i])) {
  311. const loci = reprList[i].getLoci(pickingId);
  312. if (!isEmptyLoci(loci)) return loci;
  313. }
  314. }
  315. return EmptyLoci;
  316. },
  317. getAllLoci: () => {
  318. const loci: ModelLoci[] = [];
  319. const { visuals } = currentProps;
  320. for (let i = 0, il = reprList.length; i < il; ++i) {
  321. if (!visuals || visuals.includes(reprMap[i])) {
  322. loci.push(...reprList[i].getAllLoci());
  323. }
  324. }
  325. return loci;
  326. },
  327. mark: (loci: ModelLoci, action: MarkerAction) => {
  328. let marked = false;
  329. for (let i = 0, il = reprList.length; i < il; ++i) {
  330. marked = reprList[i].mark(loci, action) || marked;
  331. }
  332. return marked;
  333. },
  334. setState: (state: Partial<S>) => {
  335. stateBuilder.update(currentState, state);
  336. for (let i = 0, il = reprList.length; i < il; ++i) {
  337. reprList[i].setState(state); // only set the new (partial) state
  338. }
  339. },
  340. setTheme: (theme: Theme) => {
  341. currentTheme = theme;
  342. for (let i = 0, il = reprList.length; i < il; ++i) {
  343. reprList[i].setTheme(theme);
  344. }
  345. },
  346. destroy() {
  347. for (let i = 0, il = reprList.length; i < il; ++i) {
  348. reprList[i].destroy();
  349. }
  350. }
  351. };
  352. }
  353. export function fromRenderObject(label: string, renderObject: GraphicsRenderObject): Representation<GraphicsRenderObject, BaseGeometry.Params> {
  354. let version = 0;
  355. const updated = new Subject<number>();
  356. const geometryState = new GeometryState();
  357. const currentState = Representation.createState();
  358. const currentTheme = Theme.createEmpty();
  359. const currentParams = PD.clone(BaseGeometry.Params);
  360. const currentProps = PD.getDefaultValues(BaseGeometry.Params);
  361. return {
  362. label,
  363. updated,
  364. get groupCount() { return renderObject.values.uGroupCount.ref.value; },
  365. get renderObjects() { return [renderObject]; },
  366. get geometryVersion() { return geometryState.version; },
  367. get props() { return currentProps; },
  368. get params() { return currentParams; },
  369. createOrUpdate: (props: Partial<PD.Values<BaseGeometry.Params>> = {}) => {
  370. const qualityProps = getQualityProps(Object.assign({}, currentProps, props));
  371. Object.assign(currentProps, props, qualityProps);
  372. return Task.create(`Updating '${label}' representation`, async runtime => {
  373. // TODO
  374. geometryState.add(0, renderObject.id);
  375. geometryState.snapshot();
  376. updated.next(version++);
  377. });
  378. },
  379. get state() { return currentState; },
  380. get theme() { return currentTheme; },
  381. getLoci: () => {
  382. // TODO
  383. return EmptyLoci;
  384. },
  385. getAllLoci: () => {
  386. // TODO
  387. return [];
  388. },
  389. mark: (loci: ModelLoci, action: MarkerAction) => {
  390. // TODO
  391. return false;
  392. },
  393. setState: (state: Partial<State>) => {
  394. if (state.visible !== undefined) Visual.setVisibility(renderObject, state.visible);
  395. if (state.alphaFactor !== undefined) Visual.setAlphaFactor(renderObject, state.alphaFactor);
  396. if (state.pickable !== undefined) Visual.setPickable(renderObject, state.pickable);
  397. if (state.colorOnly !== undefined) Visual.setColorOnly(renderObject, state.colorOnly);
  398. if (state.overpaint !== undefined) {
  399. // TODO
  400. }
  401. if (state.transparency !== undefined) {
  402. // TODO
  403. }
  404. if (state.substance !== undefined) {
  405. // TODO
  406. }
  407. if (state.themeStrength !== undefined) Visual.setThemeStrength(renderObject, state.themeStrength);
  408. if (state.transform !== undefined) Visual.setTransform(renderObject, state.transform);
  409. Representation.updateState(currentState, state);
  410. },
  411. setTheme: () => { },
  412. destroy() { }
  413. };
  414. }
  415. }