123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- import { ParamDefinition as PD } from '../mol-util/param-definition';
- import { WebGLContext } from '../mol-gl/webgl/context';
- import { ColorTheme } from '../mol-theme/color';
- import { SizeTheme } from '../mol-theme/size';
- import { ThemeRegistryContext, Theme } from '../mol-theme/theme';
- import { Subject } from 'rxjs';
- import { GraphicsRenderObject } from '../mol-gl/render-object';
- import { Task } from '../mol-task';
- import { PickingId } from '../mol-geo/geometry/picking';
- import { MarkerAction, MarkerActions } from '../mol-util/marker-action';
- import { Loci as ModelLoci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
- import { Overpaint } from '../mol-theme/overpaint';
- import { Transparency } from '../mol-theme/transparency';
- import { Mat4 } from '../mol-math/linear-algebra';
- import { getQualityProps } from './util';
- import { BaseGeometry } from '../mol-geo/geometry/base';
- import { Visual } from './visual';
- import { CustomProperty } from '../mol-model-props/common/custom-property';
- import { Clipping } from '../mol-theme/clipping';
- export type RepresentationProps = { [k: string]: any }
- export interface RepresentationContext {
- readonly webgl?: WebGLContext
- readonly colorThemeRegistry: ColorTheme.Registry
- readonly sizeThemeRegistry: SizeTheme.Registry
- }
- export type RepresentationParamsGetter<D, P extends PD.Params> = (ctx: ThemeRegistryContext, data: D) => P
- export type RepresentationFactory<D, P extends PD.Params, S extends Representation.State> = (ctx: RepresentationContext, getParams: RepresentationParamsGetter<D, P>) => Representation<D, P, S>
- export interface RepresentationProvider<D = any, P extends PD.Params = any, S extends Representation.State = any, Id extends string = string> {
- readonly name: Id,
- readonly label: string
- readonly description: string
- readonly factory: RepresentationFactory<D, P, S>
- readonly getParams: RepresentationParamsGetter<D, P>
- readonly defaultValues: PD.Values<P>
- readonly defaultColorTheme: { name: string, props?: {} }
- readonly defaultSizeTheme: { name: string, props?: {} }
- readonly isApplicable: (data: D) => boolean
- readonly ensureCustomProperties?: {
- attach: (ctx: CustomProperty.Context, data: D) => Promise<void>,
- detach: (data: D) => void
- }
- }
- export namespace RepresentationProvider {
- export type ParamValues<R extends RepresentationProvider<any, any, any>> = R extends RepresentationProvider<any, infer P, any> ? PD.Values<P> : never;
- export function getDetaultParams<R extends RepresentationProvider<D, any, any>, D>(r: R, ctx: ThemeRegistryContext, data: D) {
- return PD.getDefaultValues(r.getParams(ctx, data));
- }
- }
- export type AnyRepresentationProvider = RepresentationProvider<any, {}, Representation.State>
- export const EmptyRepresentationProvider = {
- label: '',
- description: '',
- factory: () => Representation.Empty,
- getParams: () => ({}),
- defaultValues: {}
- };
- function getTypes(list: { name: string, provider: RepresentationProvider<any, any, any> }[]) {
- return list.map(e => [e.name, e.provider.label] as [string, string]);
- }
- export class RepresentationRegistry<D, S extends Representation.State> {
- private _list: { name: string, provider: RepresentationProvider<D, any, any> }[] = []
- private _map = new Map<string, RepresentationProvider<D, any, any>>()
- private _name = new Map<RepresentationProvider<D, any, any>, string>()
- get default() { return this._list[0]; }
- get types(): [string, string][] { return getTypes(this._list); }
- constructor() {};
- add<P extends PD.Params>(provider: RepresentationProvider<D, P, S>) {
- if (this._map.has(provider.name)) {
- throw new Error(`${provider.name} already registered.`);
- }
- this._list.push({ name: provider.name, provider });
- this._map.set(provider.name, provider);
- this._name.set(provider, provider.name);
- }
- getName(provider: RepresentationProvider<D, any, any>): string {
- if (!this._name.has(provider)) throw new Error(`'${provider.label}' is not a registered represenatation provider.`);
- return this._name.get(provider)!;
- }
- remove(provider: RepresentationProvider<D, any, any>) {
- const name = provider.name;
- this._list.splice(this._list.findIndex(e => e.name === name), 1);
- const p = this._map.get(name);
- if (p) {
- this._map.delete(name);
- this._name.delete(p);
- }
- }
- get<P extends PD.Params>(name: string): RepresentationProvider<D, P, S> {
- return this._map.get(name) || EmptyRepresentationProvider as unknown as RepresentationProvider<D, P, S>;
- }
- get list() {
- return this._list;
- }
- getApplicableList(data: D) {
- return this._list.filter(e => e.provider.isApplicable(data));
- }
- getApplicableTypes(data: D) {
- return getTypes(this.getApplicableList(data));
- }
- }
- export { Representation };
- interface Representation<D, P extends PD.Params = {}, S extends Representation.State = Representation.State> {
- readonly label: string
- readonly updated: Subject<number>
-
- readonly groupCount: number
- readonly renderObjects: ReadonlyArray<GraphicsRenderObject>
- readonly props: Readonly<PD.Values<P>>
- readonly params: Readonly<P>
- readonly state: Readonly<S>
- readonly theme: Readonly<Theme>
- createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void>
- setState: (state: Partial<S>) => void
- setTheme: (theme: Theme) => void
-
- getLoci: (pickingId?: PickingId) => ModelLoci
- mark: (loci: ModelLoci, action: MarkerAction) => boolean
- destroy: () => void
- }
- namespace Representation {
- export interface Loci<T extends ModelLoci = ModelLoci> { loci: T, repr?: Representation.Any }
- export namespace Loci {
- export function areEqual(a: Loci, b: Loci) {
- return a.repr === b.repr && ModelLoci.areEqual(a.loci, b.loci);
- }
- export function isEmpty(a: Loci) {
- return ModelLoci.isEmpty(a.loci);
- }
- export const Empty: Loci = { loci: EmptyLoci };
- }
- export interface State {
-
- visible: boolean
-
- alphaFactor: number
-
- pickable: boolean
-
- overpaint: Overpaint
-
- transparency: Transparency
-
- clipping: Clipping
-
- syncManually: boolean
-
- transform: Mat4
-
- markerActions: MarkerActions
- }
- export function createState(): State {
- return { visible: true, alphaFactor: 1, pickable: true, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, clipping: Clipping.Empty, markerActions: MarkerActions.All };
- }
- export function updateState(state: State, update: Partial<State>) {
- if (update.visible !== undefined) state.visible = update.visible;
- if (update.alphaFactor !== undefined) state.alphaFactor = update.alphaFactor;
- if (update.pickable !== undefined) state.pickable = update.pickable;
- if (update.overpaint !== undefined) state.overpaint = update.overpaint;
- if (update.transparency !== undefined) state.transparency = update.transparency;
- if (update.clipping !== undefined) state.clipping = update.clipping;
- if (update.syncManually !== undefined) state.syncManually = update.syncManually;
- if (update.transform !== undefined) Mat4.copy(state.transform, update.transform);
- if (update.markerActions !== undefined) state.markerActions = update.markerActions;
- }
- export interface StateBuilder<S extends State> {
- create(): S
- update(state: S, update: Partial<S>): void
- }
- export const StateBuilder: StateBuilder<State> = { create: createState, update: updateState };
- export type Any = Representation<any, any, any>
- export const Empty: Any = {
- label: '', groupCount: 0, renderObjects: [], props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
- createOrUpdate: () => Task.constant('', undefined),
- setState: () => {},
- setTheme: () => {},
- getLoci: () => EmptyLoci,
- mark: () => false,
- destroy: () => {}
- };
- export type Def<D, P extends PD.Params = {}, S extends State = State> = { [k: string]: RepresentationFactory<D, P, S> }
- 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> {
- let version = 0;
- const updated = new Subject<number>();
- const currentState = stateBuilder.create();
- let currentTheme = Theme.createEmpty();
- let currentParams: P;
- let currentProps: PD.Values<P>;
- let currentData: D;
- const reprMap: { [k: number]: string } = {};
- const reprList: Representation<D, P>[] = Object.keys(reprDefs).map((name, i) => {
- reprMap[i] = name;
- const repr = reprDefs[name](ctx, getParams);
- repr.setState(currentState);
- return repr;
- });
- return {
- label,
- updated,
- get groupCount() {
- let groupCount = 0;
- if (currentProps) {
- const { visuals } = currentProps;
- for (let i = 0, il = reprList.length; i < il; ++i) {
- if (!visuals || visuals.includes(reprMap[i])) {
- groupCount += reprList[i].groupCount;
- }
- }
- }
- return groupCount;
- },
- get renderObjects() {
- const renderObjects: GraphicsRenderObject[] = [];
- if (currentProps) {
- const { visuals } = currentProps;
- for (let i = 0, il = reprList.length; i < il; ++i) {
- if (!visuals || visuals.includes(reprMap[i])) {
- renderObjects.push(...reprList[i].renderObjects);
- }
- }
- }
- return renderObjects;
- },
- get props() { return currentProps; },
- get params() { return currentParams; },
- createOrUpdate: (props: Partial<P> = {}, data?: D) => {
- if (data && data !== currentData) {
- currentParams = getParams(ctx, data);
- currentData = data;
- if (!currentProps) currentProps = PD.getDefaultValues(currentParams) as P;
- }
- const qualityProps = getQualityProps(Object.assign({}, currentProps, props), currentData);
- Object.assign(currentProps, props, qualityProps);
- const { visuals } = currentProps;
- return Task.create(`Creating or updating '${label}' representation`, async runtime => {
- for (let i = 0, il = reprList.length; i < il; ++i) {
- if (!visuals || visuals.includes(reprMap[i])) {
- await reprList[i].createOrUpdate(currentProps, currentData).runInContext(runtime);
- }
- }
- updated.next(version++);
- });
- },
- get state() { return currentState; },
- get theme() { return currentTheme; },
- getLoci: (pickingId?: PickingId) => {
- const { visuals } = currentProps;
- for (let i = 0, il = reprList.length; i < il; ++i) {
- if (!visuals || visuals.includes(reprMap[i])) {
- const loci = reprList[i].getLoci(pickingId);
- if (!isEmptyLoci(loci)) return loci;
- }
- }
- return EmptyLoci;
- },
- mark: (loci: ModelLoci, action: MarkerAction) => {
- let marked = false;
- for (let i = 0, il = reprList.length; i < il; ++i) {
- marked = reprList[i].mark(loci, action) || marked;
- }
- return marked;
- },
- setState: (state: Partial<S>) => {
- stateBuilder.update(currentState, state);
- for (let i = 0, il = reprList.length; i < il; ++i) {
- reprList[i].setState(currentState);
- }
- },
- setTheme: (theme: Theme) => {
- for (let i = 0, il = reprList.length; i < il; ++i) {
- reprList[i].setTheme(theme);
- }
- },
- destroy() {
- for (let i = 0, il = reprList.length; i < il; ++i) {
- reprList[i].destroy();
- }
- }
- };
- }
- export function fromRenderObject(label: string, renderObject: GraphicsRenderObject): Representation<GraphicsRenderObject, BaseGeometry.Params> {
- let version = 0;
- const updated = new Subject<number>();
- const currentState = Representation.createState();
- const currentTheme = Theme.createEmpty();
- const currentParams = PD.clone(BaseGeometry.Params);
- const currentProps = PD.getDefaultValues(BaseGeometry.Params);
- return {
- label,
- updated,
- get groupCount() { return renderObject.values.uGroupCount.ref.value; },
- get renderObjects() { return [renderObject]; },
- get props() { return currentProps; },
- get params() { return currentParams; },
- createOrUpdate: (props: Partial<PD.Values<BaseGeometry.Params>> = {}) => {
- const qualityProps = getQualityProps(Object.assign({}, currentProps, props));
- Object.assign(currentProps, props, qualityProps);
- return Task.create(`Updating '${label}' representation`, async runtime => {
-
- updated.next(version++);
- });
- },
- get state() { return currentState; },
- get theme() { return currentTheme; },
- getLoci: () => {
-
- return EmptyLoci;
- },
- mark: (loci: ModelLoci, action: MarkerAction) => {
-
- return false;
- },
- setState: (state: Partial<State>) => {
- if (state.visible !== undefined) Visual.setVisibility(renderObject, state.visible);
- if (state.alphaFactor !== undefined) Visual.setAlphaFactor(renderObject, state.alphaFactor);
- if (state.pickable !== undefined) Visual.setPickable(renderObject, state.pickable);
- if (state.overpaint !== undefined) {
-
- }
- if (state.transparency !== undefined) {
-
- }
- if (state.transform !== undefined) Visual.setTransform(renderObject, state.transform);
- Representation.updateState(currentState, state);
- },
- setTheme: () => { },
- destroy() { }
- };
- }
- }
|