diff --git a/src/examples/basic-wrapper/custom-theme.ts b/src/examples/basic-wrapper/custom-theme.ts new file mode 100644 index 0000000000000000000000000000000000000000..583e1a774ecabed7cdf514547fc463bfab63eb67 --- /dev/null +++ b/src/examples/basic-wrapper/custom-theme.ts @@ -0,0 +1,50 @@ +import { isPositionLocation } from '../../mol-geo/util/location-iterator'; +import { Vec3 } from '../../mol-math/linear-algebra'; +import { ColorTheme } from '../../mol-theme/color'; +import { ThemeDataContext } from '../../mol-theme/theme'; +import { Color } from '../../mol-util/color'; +import { ColorNames } from '../../mol-util/color/names'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; + +export function CustomColorTheme( + ctx: ThemeDataContext, + props: PD.Values<{}> +): ColorTheme<{}> { + const { radius, center } = ctx.structure?.boundary.sphere!; + const radiusSq = Math.max(radius * radius, 0.001); + const scale = ColorTheme.PaletteScale; + + return { + factory: CustomColorTheme, + granularity: 'vertex', + color: location => { + if (!isPositionLocation(location)) return ColorNames.black; + const dist = Vec3.squaredDistance(location.position, center); + const t = Math.min(dist / radiusSq, 1); + return ((t * scale) | 0) as Color; + }, + palette: { + colors: [ + ColorNames.red, + ColorNames.pink, + ColorNames.violet, + ColorNames.orange, + ColorNames.yellow, + ColorNames.green, + ColorNames.blue + ] + }, + props: props, + description: '', + }; +} + +export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = { + name: 'basic-wrapper-custom-color-theme', + label: 'Custom Color Theme', + category: ColorTheme.Category.Misc, + factory: CustomColorTheme, + getParams: () => ({}), + defaultValues: { }, + isApplicable: (ctx: ThemeDataContext) => true, +}; diff --git a/src/examples/basic-wrapper/index.html b/src/examples/basic-wrapper/index.html index 43f09e0b9981533afb17ccdcf3ea12d801338163..2de1b68727b01220995231e93343471d013bd75a 100644 --- a/src/examples/basic-wrapper/index.html +++ b/src/examples/basic-wrapper/index.html @@ -97,6 +97,7 @@ addHeader('Misc'); addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes()); + addControl('Apply Custom Theme', () => BasicMolStarWrapper.coloring.applyCustomTheme()); addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault()); addHeader('Interactivity'); diff --git a/src/examples/basic-wrapper/index.ts b/src/examples/basic-wrapper/index.ts index 68f8708736e2c516400b3d69d4fd38eea89d9d63..1bf4a5d4d7758b3a6a379199cc799f60f8da0fce 100644 --- a/src/examples/basic-wrapper/index.ts +++ b/src/examples/basic-wrapper/index.ts @@ -18,6 +18,7 @@ import { Asset } from '../../mol-util/assets'; import { Color } from '../../mol-util/color'; import { StripedResidues } from './coloring'; import { CustomToastMessage } from './controls'; +import { CustomColorThemeProvider } from './custom-theme'; import './index.html'; import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition'; require('mol-plugin-ui/skin/light.scss'); @@ -42,6 +43,7 @@ class BasicWrapper { }); this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!); + this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider); this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!); this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true); } @@ -103,6 +105,13 @@ class BasicWrapper { } }); }, + applyCustomTheme: async () => { + this.plugin.dataTransaction(async () => { + for (const s of this.plugin.managers.structure.hierarchy.current.structures) { + await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: CustomColorThemeProvider.name as any }); + } + }); + }, applyDefault: async () => { this.plugin.dataTransaction(async () => { for (const s of this.plugin.managers.structure.hierarchy.current.structures) { diff --git a/src/mol-geo/geometry/color-data.ts b/src/mol-geo/geometry/color-data.ts index 3d9b4577cf632764fc59a4e9e9cbf63a69e1ea6b..63aab0939c83799723f80b2ff7f65943b53eac5c 100644 --- a/src/mol-geo/geometry/color-data.ts +++ b/src/mol-geo/geometry/color-data.ts @@ -18,11 +18,24 @@ export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 've export type ColorData = { uColor: ValueCell<Vec3>, tColor: ValueCell<TextureImage<Uint8Array>>, + tPalette: ValueCell<TextureImage<Uint8Array>>, uColorTexDim: ValueCell<Vec2>, dColorType: ValueCell<string>, + dUsePalette: ValueCell<boolean>, } export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData { + const data = _createColors(locationIt, positionIt, colorTheme, colorData); + if (colorTheme.palette) { + ValueCell.updateIfChanged(data.dUsePalette, true); + updatePaletteTexture(colorTheme.palette, data.tPalette); + } else { + ValueCell.updateIfChanged(data.dUsePalette, false); + } + return data; +} + +function _createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData { switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) { case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData); case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData); @@ -42,18 +55,20 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData return { uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)), tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }), + tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }), uColorTexDim: ValueCell.create(Vec2.create(1, 1)), dColorType: ValueCell.create('uniform'), + dUsePalette: ValueCell.create(false), }; } } /** Creates color uniform */ -export function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { +function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { return createValueColor(color(NullLocation, false), colorData); } -export function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData { +function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData { if (colorData) { ValueCell.update(colorData.tColor, colors); ValueCell.update(colorData.uColorTexDim, Vec2.create(colors.width, colors.height)); @@ -63,14 +78,16 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color return { uColor: ValueCell.create(Vec3()), tColor: ValueCell.create(colors), + tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }), uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)), dColorType: ValueCell.create(type), + dUsePalette: ValueCell.create(false), }; } } /** Creates color texture with color for each instance */ -export function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { +function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { instanceCount } = locationIt; const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); locationIt.reset(); @@ -83,7 +100,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio } /** Creates color texture with color for each group (i.e. shared across instances) */ -export function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { +function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { groupCount } = locationIt; const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); locationIt.reset(); @@ -95,7 +112,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo } /** Creates color texture with color for each group in each instance */ -export function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { +function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { groupCount, instanceCount } = locationIt; const count = instanceCount * groupCount; const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); @@ -108,7 +125,7 @@ export function createGroupInstanceColor(locationIt: LocationIterator, color: Lo } /** Creates color texture with color for each vertex (i.e. shared across instances) */ -export function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { +function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { groupCount, stride } = locationIt; const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); locationIt.reset(); @@ -124,7 +141,7 @@ export function createVertexColor(locationIt: LocationIterator, color: LocationC } /** Creates color texture with color for each vertex in each instance */ -export function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { +function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { groupCount, instanceCount, stride } = locationIt; const count = instanceCount * groupCount; const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); @@ -138,3 +155,34 @@ export function createVertexInstanceColor(locationIt: LocationIterator, color: L } return createTextureColor(colors, 'vertexInstance', colorData); } + +function updatePaletteTexture(palette: ColorTheme.Palette, cell: ValueCell<TextureImage<Uint8Array>>) { + let isSynced = true; + const texture = cell.ref.value; + if (palette.colors.length !== texture.width) { + isSynced = false; + } else { + const data = texture.array; + let o = 0; + for (const c of palette.colors) { + const [r, g, b] = Color.toRgb(c); + if (data[o++] !== r || data[o++] !== g || data[o++] !== b) { + isSynced = false; + break; + } + } + } + + if (isSynced) return; + + const array = new Uint8Array(palette.colors.length * 3); + let o = 0; + for (const c of palette.colors) { + const [r, g, b] = Color.toRgb(c); + array[o++] = r; + array[o++] = g; + array[o++] = b; + } + + ValueCell.update(cell, { array, height: 1, width: palette.colors.length }); +} \ No newline at end of file diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index ab9c319d4644cd42e0f40e2f7674c8cdce5ad7e8..89d2d2b14071ad89394e81337cdbd1d1259d2ae0 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -185,7 +185,9 @@ export const ColorSchema = { uColor: UniformSpec('v3', 'material'), uColorTexDim: UniformSpec('v2'), tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'), + tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'linear'), dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']), + dUsePalette: DefineSpec('boolean'), } as const; export type ColorSchema = typeof ColorSchema export type ColorValues = Values<ColorSchema> diff --git a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts index 040d303fade6c7f9deab8efbfe4b01f31fb52c27..6653f8302350b36132f818e020a576caf539a8c5 100644 --- a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts @@ -14,6 +14,10 @@ export const assign_color_varying = ` vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb; #endif + #ifdef dUsePalette + vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / 16777215.0; + #endif + #ifdef dOverpaint vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim); #endif diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts index 0678b12c841ee094cae5c7562b4ad618505a416c..247a2b6caf1a7dcbdea2a060237bd47a70db920e 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts @@ -1,6 +1,8 @@ export const assign_material_color = ` #if defined(dRenderVariant_color) - #if defined(dColorType_uniform) + #if defined(dUsePalette) + vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha); + #elif defined(dColorType_uniform) vec4 material = vec4(uColor, uAlpha); #elif defined(dColorType_varying) vec4 material = vec4(vColor.rgb, uAlpha); diff --git a/src/mol-gl/shader/chunks/color-frag-params.glsl.ts b/src/mol-gl/shader/chunks/color-frag-params.glsl.ts index 6bb536b49ec8e5b891e5a44a3ea075fd1ebbb798..e89211cfbd40c3e9e094982b7893288d7a975506 100644 --- a/src/mol-gl/shader/chunks/color-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/color-frag-params.glsl.ts @@ -21,4 +21,9 @@ export const color_frag_params = ` varying float vGroup; varying float vTransparency; #endif + +#ifdef dUsePalette + uniform sampler2D tPalette; + varying float vPaletteV; +#endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts index 46c96138f3ebc4eb3656990dfd465c7540b40c52..ab3e2a790d91c9d50b65ec3018c198f86befa0ab 100644 --- a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts +++ b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts @@ -30,4 +30,8 @@ export const color_vert_params = ` uniform vec2 uTransparencyTexDim; uniform sampler2D tTransparency; #endif + +#ifdef dUsePalette + varying float vPaletteV; +#endif `; \ No newline at end of file diff --git a/src/mol-theme/color.ts b/src/mol-theme/color.ts index 76df06a71f32fa9dbc315c0cc44cf686a50ba1cd..6ca6ee7af325331b65ed05f69b61f8b8530ac31c 100644 --- a/src/mol-theme/color.ts +++ b/src/mol-theme/color.ts @@ -44,6 +44,9 @@ interface ColorTheme<P extends PD.Params> { readonly granularity: ColorType readonly color: LocationColor readonly props: Readonly<PD.Values<P>> + // if palette is defined, 24bit RGB color value normalized to interval [0, 1] + // is used as index to the colors + readonly palette?: Readonly<ColorTheme.Palette> readonly contextHash?: number readonly description?: string readonly legend?: Readonly<ScaleLegend | TableLegend> @@ -58,6 +61,12 @@ namespace ColorTheme { Misc = 'Miscellaneous', } + export interface Palette { + colors: Color[] + } + + export const PaletteScale = (1 << 24) - 1; + export type Props = { [k: string]: any } export type Factory<P extends PD.Params> = (ctx: ThemeDataContext, props: PD.Values<P>) => ColorTheme<P> export const EmptyFactory = () => Empty;