diff --git a/src/apps/docking-viewer/viewport.tsx b/src/apps/docking-viewer/viewport.tsx index 74209873232d3c425f9ab086cf21c3ebc675e237..082c59dd612c870c593636f3d8ab1b73cd7181ef 100644 --- a/src/apps/docking-viewer/viewport.tsx +++ b/src/apps/docking-viewer/viewport.tsx @@ -76,7 +76,7 @@ const PresetParams = { ...StructureRepresentationPresetProvider.CommonParams, }; -const CustomMaterial = Material.fromObjectNormalized({ roughness: 0.2, metalness: 0 }); +const CustomMaterial = Material({ roughness: 0.2, metalness: 0 }); export const StructurePreset = StructureRepresentationPresetProvider({ id: 'preset-structure', diff --git a/src/mol-geo/geometry/base.ts b/src/mol-geo/geometry/base.ts index c037a0513f28f92c60c3f11edbd1eb53331e32fc..acf50367c82b7652a9e0f4ee1d696e606e26b4cc 100644 --- a/src/mol-geo/geometry/base.ts +++ b/src/mol-geo/geometry/base.ts @@ -97,24 +97,22 @@ export namespace BaseGeometry { } export function createValues(props: PD.Values<Params>, counts: Counts) { - const { metalness, roughness } = Material.toObjectNormalized(props.material); return { alpha: ValueCell.create(props.alpha), uAlpha: ValueCell.create(props.alpha), uVertexCount: ValueCell.create(counts.vertexCount), uGroupCount: ValueCell.create(counts.groupCount), drawCount: ValueCell.create(counts.drawCount), - uMetalness: ValueCell.create(metalness), - uRoughness: ValueCell.create(roughness), + uMetalness: ValueCell.create(props.material.metalness), + uRoughness: ValueCell.create(props.material.roughness), dLightCount: ValueCell.create(1), }; } export function updateValues(values: BaseValues, props: PD.Values<Params>) { - const { metalness, roughness } = Material.toObjectNormalized(props.material); ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render - ValueCell.updateIfChanged(values.uMetalness, metalness); - ValueCell.updateIfChanged(values.uRoughness, roughness); + ValueCell.updateIfChanged(values.uMetalness, props.material.metalness); + ValueCell.updateIfChanged(values.uRoughness, props.material.roughness); } export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState { diff --git a/src/mol-plugin-state/helpers/structure-substance.ts b/src/mol-plugin-state/helpers/structure-substance.ts index cd9942383fd4f5351fd79eb6d772370cc83caa9f..63d819be442ddfec2dbb52973e7d5226590a48f0 100644 --- a/src/mol-plugin-state/helpers/structure-substance.ts +++ b/src/mol-plugin-state/helpers/structure-substance.ts @@ -18,7 +18,7 @@ import { Material } from '../../mol-util/material'; type SubstanceEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, substance?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.SubstanceStructureRepresentation3DFromBundle>>) => Promise<void> const SubstanceManagerTag = 'substance-controls'; -export async function setStructureSubstance(plugin: PluginContext, components: StructureComponentRef[], material: Material | -1, lociGetter: (structure: Structure) => Promise<StructureElement.Loci | EmptyLoci>, types?: string[]) { +export async function setStructureSubstance(plugin: PluginContext, components: StructureComponentRef[], material: Material | undefined, lociGetter: (structure: Structure) => Promise<StructureElement.Loci | EmptyLoci>, types?: string[]) { await eachRepr(plugin, components, async (update, repr, substanceCell) => { if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return; @@ -30,8 +30,8 @@ export async function setStructureSubstance(plugin: PluginContext, components: S const layer = { bundle: StructureElement.Bundle.fromLoci(loci), - material: material === -1 ? Material(0) : material, - clear: material === -1 + material: material ?? Material(), + clear: !material }; if (substanceCell) { diff --git a/src/mol-plugin-state/manager/structure/component.ts b/src/mol-plugin-state/manager/structure/component.ts index f39b8baf173348e8572d30f7155f2ae6496a564a..cbfb1cbeae75f0520621ca903ef124702d040d65 100644 --- a/src/mol-plugin-state/manager/structure/component.ts +++ b/src/mol-plugin-state/manager/structure/component.ts @@ -388,7 +388,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone const p = params.action.params; await setStructureSubstance(this.plugin, s.components, p.material, getLoci, params.representations); } else if (params.action.name === 'resetMaterial') { - await setStructureSubstance(this.plugin, s.components, -1, getLoci, params.representations); + await setStructureSubstance(this.plugin, s.components, void 0, getLoci, params.representations); } else if (params.action.name === 'clipping') { const p = params.action.params; await setStructureClipping(this.plugin, s.components, Clipping.Groups.fromNames(p.excludeGroups), getLoci, params.representations); diff --git a/src/mol-plugin-state/transforms/representation.ts b/src/mol-plugin-state/transforms/representation.ts index c519943574acee51c50cdcb614bb248f4fa1d875..1f1a4c757e4470333c73851cd970c1abcb4c4005 100644 --- a/src/mol-plugin-state/transforms/representation.ts +++ b/src/mol-plugin-state/transforms/representation.ts @@ -547,7 +547,7 @@ const SubstanceStructureRepresentation3DFromScript = PluginStateTransform.BuiltI }, e => `${e.clear ? 'Clear' : Material.toString(e.material)}`, { defaultValue: [{ script: Script('(sel.atom.all)', 'mol-script'), - material: Material.fromNormalized(0, 1), + material: Material({ roughness: 1 }), clear: false }] }), @@ -604,7 +604,7 @@ const SubstanceStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI }, e => `${e.clear ? 'Clear' : Material.toString(e.material)}`, { defaultValue: [{ bundle: StructureElement.Bundle.Empty, - material: Material.fromNormalized(0, 1), + material: Material({ roughness: 1 }), clear: false }], isHidden: true diff --git a/src/mol-theme/substance.ts b/src/mol-theme/substance.ts index ad2b0c8b0443a580ddce89bfe36eb81437937e2f..104eb9097d0df4eaa4badaf10a92de1c07f4766a 100644 --- a/src/mol-theme/substance.ts +++ b/src/mol-theme/substance.ts @@ -8,6 +8,7 @@ import { Loci } from '../mol-model/loci'; import { Structure, StructureElement } from '../mol-model/structure'; import { Script } from '../mol-script/script'; import { Material } from '../mol-util/material'; +import { shallowEqual } from '../mol-util/object'; export { Substance }; @@ -26,7 +27,7 @@ namespace Substance { if (sA.layers.length !== sB.layers.length) return false; for (let i = 0, il = sA.layers.length; i < il; ++i) { if (sA.layers[i].clear !== sB.layers[i].clear) return false; - if (sA.layers[i].material !== sB.layers[i].material) return false; + if (!shallowEqual(sA.layers[i].material, sB.layers[i].material)) return false; if (!Loci.areEqual(sA.layers[i].loci, sB.layers[i].loci)) return false; } return true; @@ -51,25 +52,32 @@ namespace Substance { export function merge(substance: Substance): Substance { if (isEmpty(substance)) return substance; const { structure } = substance.layers[0].loci; - const map = new Map<Material | -1, StructureElement.Loci>(); + let clearLoci: StructureElement.Loci | undefined = void 0; + const map = new Map<Material, StructureElement.Loci>(); let shadowed = StructureElement.Loci.none(structure); for (let i = 0, il = substance.layers.length; i < il; ++i) { let { loci, material, clear } = substance.layers[il - i - 1]; // process from end loci = StructureElement.Loci.subtract(loci, shadowed); shadowed = StructureElement.Loci.union(loci, shadowed); if (!StructureElement.Loci.isEmpty(loci)) { - const materialOrClear = clear ? -1 : material; - if (map.has(materialOrClear)) { - loci = StructureElement.Loci.union(loci, map.get(materialOrClear)!); + if (clear) { + clearLoci = clearLoci + ? StructureElement.Loci.union(loci, clearLoci) + : loci; + } else { + if (map.has(material)) { + loci = StructureElement.Loci.union(loci, map.get(material)!); + } + map.set(material, loci); } - map.set(materialOrClear, loci); } } const layers: Substance.Layer[] = []; - map.forEach((loci, materialOrClear) => { - const clear = materialOrClear === -1; - const material = clear ? Material(0) : materialOrClear; - layers.push({ loci, material, clear }); + if (clearLoci) { + layers.push({ loci: clearLoci, material: Material(), clear: true }); + } + map.forEach((loci, material) => { + layers.push({ loci, material, clear: false }); }); return { layers }; } diff --git a/src/mol-util/material.ts b/src/mol-util/material.ts index a244d56fa1d74f546bd8c691b4b464b4dc765ee5..84adddde28204d73082b85110fbe410b3e57399f 100644 --- a/src/mol-util/material.ts +++ b/src/mol-util/material.ts @@ -6,58 +6,43 @@ import { NumberArray } from './type-helpers'; import { ParamDefinition as PD } from './param-definition'; -import { toFixed } from './number'; -/** Material properties expressed as a single number */ -export type Material = { readonly '@type': 'material' } & number +export interface Material { + /** Normalized to [0, 1] range */ + metalness: number, + /** Normalized to [0, 1] range */ + roughness: number +} -export function Material(hex: number) { return hex as Material; } +export function Material(values?: Partial<Material>) { + return { ...Material.Zero, ...values }; +} export namespace Material { - export function fromNormalized(metalness: number, roughness: number): Material { - return (((metalness * 255) << 16) | ((roughness * 255) << 8)) as Material; - } - - export function fromObjectNormalized(v: { metalness: number, roughness: number }): Material { - return fromNormalized(v.metalness, v.roughness); - } - - export function toObjectNormalized(material: Material, fractionDigits?: number) { - const metalness = (material >> 16 & 255) / 255; - const roughness = (material >> 8 & 255) / 255; - return { - metalness: fractionDigits ? toFixed(metalness, fractionDigits) : metalness, - roughness: fractionDigits ? toFixed(roughness, fractionDigits) : roughness - }; - } + export const Zero: Material = { metalness: 0, roughness: 0 }; export function toArray(material: Material, array: NumberArray, offset: number) { - array[offset] = (material >> 16 & 255); - array[offset + 1] = (material >> 8 & 255); + array[offset] = material.metalness * 255; + array[offset + 1] = material.roughness * 255; return array; } - export function toString(material: Material) { - const metalness = (material >> 16 & 255) / 255; - const roughness = (material >> 8 & 255) / 255; - return `M ${metalness} | R ${roughness}`; + export function toString({ metalness, roughness }: Material) { + return `M ${metalness.toFixed(2)} | R ${roughness.toFixed(2)}`; } export function getParam(info?: { isExpanded?: boolean, isFlat?: boolean }) { - return PD.Converted( - (v: Material) => toObjectNormalized(v, 2), - (v: { metalness: number, roughness: number }) => fromObjectNormalized(v), - PD.Group({ - metalness: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }), - roughness: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }), - }, { - ...info, - presets: [ - [{ metalness: 0, roughness: 1 }, 'Matte'], - [{ metalness: 0.5, roughness: 0.5 }, 'Metallic'], - [{ metalness: 0, roughness: 0.25 }, 'Plastic'], - ] - }) - ); + return PD.Group({ + metalness: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }), + roughness: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }), + }, { + ...info, + presets: [ + [{ metalness: 0, roughness: 1 }, 'Matte'], + [{ metalness: 0, roughness: 0.2 }, 'Plastic'], + [{ metalness: 0, roughness: 0.6 }, 'Glossy'], + [{ metalness: 1.0, roughness: 0.6 }, 'Metallic'], + ] + }); } }