diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index e803e1e18137fb68787e7cc4d36db8507eb2201e..e4bb91a6478cdd47c652ba66c949f382b3e41d0f 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -26,15 +26,17 @@ import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; import { Color } from 'mol-util/color'; import { Camera } from './camera'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; -export const DefaultCanvas3DProps = { +export const Canvas3DParams = { // TODO: FPS cap? - // maxFps: 30, - cameraPosition: Vec3.create(0, 0, 50), - cameraMode: 'perspective' as Camera.Mode, - backgroundColor: Color(0x000000), + // maxFps: PD.Numeric(30), + cameraPosition: PD.Vec3(Vec3.create(0, 0, 50)), // TODO or should it be in a seperate 'state' property? + cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]), + backgroundColor: PD.Color(Color(0x000000)), + pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }), } -export type Canvas3DProps = typeof DefaultCanvas3DProps +export type Canvas3DParams = typeof Canvas3DParams export { Canvas3D } @@ -64,18 +66,18 @@ interface Canvas3D { readonly camera: Camera downloadScreenshot: () => void getImageData: (variant: RenderVariant) => ImageData - setProps: (props: Partial<Canvas3DProps>) => void + setProps: (props: Partial<PD.Values<Canvas3DParams>>) => void /** Returns a copy of the current Canvas3D instance props */ - readonly props: Canvas3DProps + readonly props: PD.Values<Canvas3DParams> readonly input: InputObserver readonly stats: RendererStats dispose: () => void } namespace Canvas3D { - export function create(canvas: HTMLCanvasElement, container: Element, props: Partial<Canvas3DProps> = {}): Canvas3D { - const p = { ...props, ...DefaultCanvas3DProps } + export function create(canvas: HTMLCanvasElement, container: Element, props: Partial<PD.Values<Canvas3DParams>> = {}): Canvas3D { + const p = { ...PD.getDefaultValues(Canvas3DParams), ...props } const reprRenderObjects = new Map<Representation.Any, Set<RenderObject>>() const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>() @@ -353,13 +355,16 @@ namespace Canvas3D { } }, didDraw, - setProps: (props: Partial<Canvas3DProps>) => { + setProps: (props: Partial<PD.Values<Canvas3DParams>>) => { if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) { camera.setState({ mode: props.cameraMode }) } if (props.backgroundColor !== undefined && props.backgroundColor !== renderer.props.clearColor) { renderer.setClearColor(props.backgroundColor) } + if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== renderer.props.pickingAlphaThreshold) { + renderer.setPickingAlphaThreshold(props.pickingAlphaThreshold) + } requestDraw(true) }, @@ -367,7 +372,8 @@ namespace Canvas3D { return { cameraPosition: Vec3.clone(camera.position), cameraMode: camera.state.mode, - backgroundColor: renderer.props.clearColor + backgroundColor: renderer.props.clearColor, + pickingAlphaThreshold: renderer.props.pickingAlphaThreshold, } }, get input() { diff --git a/src/mol-geo/geometry/geometry.ts b/src/mol-geo/geometry/geometry.ts index 190e21d27a4cf9ebfe361c2d1b280902e26ebef9..8533bc34244aa8a4691e389d249d7e50655aeee2 100644 --- a/src/mol-geo/geometry/geometry.ts +++ b/src/mol-geo/geometry/geometry.ts @@ -104,6 +104,7 @@ export namespace Geometry { export function createRenderableState(props: PD.Values<Geometry.Params>): RenderableState { return { visible: true, + pickable: true, depthMask: props.depthMask } } diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 51552ebf2dac61950224535024a725c0574b78d6..e62c5415f7be59b638ff97167f2ca012d6d2c34a 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -85,6 +85,7 @@ function createPoints() { } const state: RenderableState = { visible: true, + pickable: true, depthMask: true, } diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index 370437898cfee60a0df4e37d155640412c21a6fb..6d9d55ac3e5612ea01df7663062484f09dcf4503 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -9,9 +9,11 @@ import { RenderableValues, Values, RenderableSchema } from './renderable/schema' import { RenderVariant, RenderItem } from './webgl/render-item'; import { Sphere3D } from 'mol-math/geometry'; import { Vec3 } from 'mol-math/linear-algebra'; +import { ValueCell } from 'mol-util'; export type RenderableState = { visible: boolean + pickable: boolean depthMask: boolean } @@ -41,7 +43,12 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem: }, get opaque () { return values.uAlpha && values.uAlpha.ref.value === 1 }, - render: (variant: RenderVariant) => renderItem.render(variant), + render: (variant: RenderVariant) => { + if (values.uPickable) { + ValueCell.updateIfChanged(values.uPickable, state.pickable ? 1 : 0) + } + renderItem.render(variant) + }, getProgram: (variant: RenderVariant) => renderItem.getProgram(variant), update: () => renderItem.update(), dispose: () => renderItem.destroy() diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 63b2424ed608744a5e6b6651d284b2a991a5412a..27ce4e40668fb0b78c587187fbb6564d0fb006f3 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -57,7 +57,8 @@ export type DirectVolumeValues = Values<DirectVolumeSchema> export function DirectVolumeRenderable(ctx: WebGLContext, id: number, values: DirectVolumeValues, state: RenderableState): Renderable<DirectVolumeValues> { const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema } const internalValues: InternalValues = { - uObjectId: ValueCell.create(id) + uObjectId: ValueCell.create(id), + uPickable: ValueCell.create(state.pickable ? 1 : 0) } const shaderCode = DirectVolumeShaderCode const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }) diff --git a/src/mol-gl/renderable/lines.ts b/src/mol-gl/renderable/lines.ts index 31ebfd493eca89c14021d916f7dc81a93987a62e..54e62c5b448bf5b4bf8bcf88037092564fcf52ce 100644 --- a/src/mol-gl/renderable/lines.ts +++ b/src/mol-gl/renderable/lines.ts @@ -28,7 +28,8 @@ export type LinesValues = Values<LinesSchema> export function LinesRenderable(ctx: WebGLContext, id: number, values: LinesValues, state: RenderableState): Renderable<LinesValues> { const schema = { ...GlobalUniformSchema, ...InternalSchema, ...LinesSchema } const internalValues: InternalValues = { - uObjectId: ValueCell.create(id) + uObjectId: ValueCell.create(id), + uPickable: ValueCell.create(state.pickable ? 1 : 0) } const shaderCode = LinesShaderCode const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }) diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index e79f78159a7ecbc1500a6adbfe796d08b1b4b7af..a3d70909409dda202f1649ab5397ae0cc47e8d29 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -26,7 +26,8 @@ export type MeshValues = Values<MeshSchema> export function MeshRenderable(ctx: WebGLContext, id: number, values: MeshValues, state: RenderableState): Renderable<MeshValues> { const schema = { ...GlobalUniformSchema, ...InternalSchema, ...MeshSchema } const internalValues: InternalValues = { - uObjectId: ValueCell.create(id) + uObjectId: ValueCell.create(id), + uPickable: ValueCell.create(state.pickable ? 1 : 0) } const shaderCode = MeshShaderCode const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }) diff --git a/src/mol-gl/renderable/points.ts b/src/mol-gl/renderable/points.ts index 95a45cceda95c18e52b6c1829b94b8deeff5169b..f4d87921f388afdefe195965d0b290dc6465ea9e 100644 --- a/src/mol-gl/renderable/points.ts +++ b/src/mol-gl/renderable/points.ts @@ -25,7 +25,8 @@ export type PointsValues = Values<PointsSchema> export function PointsRenderable(ctx: WebGLContext, id: number, values: PointsValues, state: RenderableState): Renderable<PointsValues> { const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointsSchema } const internalValues: InternalValues = { - uObjectId: ValueCell.create(id) + uObjectId: ValueCell.create(id), + uPickable: ValueCell.create(state.pickable ? 1 : 0) } const shaderCode = PointsShaderCode const renderItem = createRenderItem(ctx, 'points', shaderCode, schema, { ...values, ...internalValues }) diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index bac604bea5230fd1a8f6eb2d8f453d14a9247d00..7050ee13bbd19865f39421d3722a6046e71ff6e0 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -149,12 +149,15 @@ export const GlobalUniformSchema = { uFogNear: UniformSpec('f'), uFogFar: UniformSpec('f'), uFogColor: UniformSpec('v3'), + + uPickingAlphaThreshold: UniformSpec('f'), } export type GlobalUniformSchema = typeof GlobalUniformSchema export type GlobalUniformValues = { [k in keyof GlobalUniformSchema]: ValueCell<any> } export const InternalSchema = { uObjectId: UniformSpec('i'), + uPickable: UniformSpec('i'), } export type InternalSchema = typeof InternalSchema export type InternalValues = { [k in keyof InternalSchema]: ValueCell<any> } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index c7d4ce3a4fec1c3b17a1dd68cd02d8d020f80a49..26c1845e523e11baea123a2c13a9569b7222a0a9 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -39,20 +39,22 @@ interface Renderer { render: (scene: Scene, variant: RenderVariant) => void setViewport: (x: number, y: number, width: number, height: number) => void setClearColor: (color: Color) => void + setPickingAlphaThreshold: (value: number) => void getImageData: () => ImageData dispose: () => void } export const DefaultRendererProps = { clearColor: Color(0x000000), - viewport: Viewport.create(0, 0, 0, 0) + viewport: Viewport.create(0, 0, 0, 0), + pickingAlphaThreshold: 0.5, } export type RendererProps = typeof DefaultRendererProps namespace Renderer { export function create(ctx: WebGLContext, camera: Camera, props: Partial<RendererProps> = {}): Renderer { const { gl } = ctx - let { clearColor, viewport: _viewport } = { ...DefaultRendererProps, ...props } + let { clearColor, viewport: _viewport, pickingAlphaThreshold } = { ...DefaultRendererProps, ...props } const viewport = Viewport.clone(_viewport) const viewportVec4 = Viewport.toVec4(Vec4.zero(), viewport) @@ -98,6 +100,8 @@ namespace Renderer { uFogNear: ValueCell.create(camera.state.near), uFogFar: ValueCell.create(camera.state.far / 50), uFogColor: ValueCell.create(Vec3.clone(fogColor)), + + uPickingAlphaThreshold: ValueCell.create(pickingAlphaThreshold), } let currentProgramId = -1 @@ -175,6 +179,10 @@ namespace Renderer { render, setClearColor, + setPickingAlphaThreshold: (value: number) => { + pickingAlphaThreshold = value + ValueCell.update(globalUniforms.uPickingAlphaThreshold, pickingAlphaThreshold) + }, setViewport: (x: number, y: number, width: number, height: number) => { Viewport.set(viewport, x, y, width, height) gl.viewport(x, y, width, height) @@ -192,6 +200,7 @@ namespace Renderer { get props() { return { clearColor, + pickingAlphaThreshold, viewport } }, diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl b/src/mol-gl/shader/chunks/assign-material-color.glsl index bc56340c25f74d749e9c8cc6377ac9ca265b2035..81758eabcd3b6630f7e7cd1cf3f2f819f419695e 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl @@ -3,5 +3,5 @@ #elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance) vec4 material = vec4(vColor.rgb, uAlpha); #elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) - vec4 material = vColor; + vec4 material = uPickable == 1 ? vColor : vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id #endif \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl b/src/mol-gl/shader/chunks/common-frag-params.glsl index c757d1b2f5afa54efad4236d533b9f4535bc64da..dc62dbe9bec92fae2018665d136b5f10e624f71b 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl @@ -12,4 +12,6 @@ uniform float uFogNear; uniform float uFogFar; uniform vec3 uFogColor; -uniform float uAlpha; \ No newline at end of file +uniform float uAlpha; +uniform float uPickingAlphaThreshold; +uniform int uPickable; \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag index 6be0ab731366d502314f61722f6b124b308dc092..7baa242c02aae1f90c6b47ebf9678152ff1f3cef 100644 --- a/src/mol-gl/shader/direct-volume.frag +++ b/src/mol-gl/shader/direct-volume.frag @@ -123,6 +123,11 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) { tmp = ((prevValue - uIsoValue) / ((prevValue - uIsoValue) - (value - uIsoValue))); isoPos = mix(pos - step, pos, tmp); + #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) + if (uAlpha < uPickingAlphaThreshold) + discard; // ignore so the element below can be picked + #else + #if defined(dColorType_objectPicking) return vec4(encodeIdRGB(float(uObjectId)), 1.0); #elif defined(dColorType_instancePicking) diff --git a/src/mol-gl/shader/lines.frag b/src/mol-gl/shader/lines.frag index 98caeadbf7fd714c79fb17425b2fd7f44ec24de8..ec512988b00b2fabea4fc65183284fc200a3d347 100644 --- a/src/mol-gl/shader/lines.frag +++ b/src/mol-gl/shader/lines.frag @@ -14,6 +14,8 @@ void main(){ #pragma glslify: import('./chunks/assign-material-color.glsl') #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) + if (uAlpha < uPickingAlphaThreshold) + discard; // ignore so the element below can be picked gl_FragColor = material; #else gl_FragColor = material; diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index 2195060272cbc736d150e39d3d58166569d1f2b3..93858961510496217a7121ee8182aee085218313 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/src/mol-gl/shader/mesh.frag @@ -33,7 +33,8 @@ void main() { #pragma glslify: import('./chunks/assign-material-color.glsl') #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) - // gl_FragColor = vec4(material.r, material.g, material.a, 1.0); + if (uAlpha < uPickingAlphaThreshold) + discard; // ignore so the element below can be picked gl_FragColor = material; #else // determine surface to light direction diff --git a/src/mol-gl/shader/points.frag b/src/mol-gl/shader/points.frag index 5ef4153d4cfc66a9f10177a2eca17ecc3a836c36..67f8aa47d44abdbfe229c868e2a64d1e12e16374 100644 --- a/src/mol-gl/shader/points.frag +++ b/src/mol-gl/shader/points.frag @@ -21,6 +21,8 @@ void main(){ #pragma glslify: import('./chunks/assign-material-color.glsl') #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) + if (uAlpha < uPickingAlphaThreshold) + discard; // ignore so the element below can be picked gl_FragColor = material; #else gl_FragColor = material; diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index e14fb754a15fa825b4b1b98dcfda5593a076c65e..d0429a36dfee714b53ba6de3a59c0cae93856e9a 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -232,6 +232,7 @@ function getGaussianDensityRenderObject(webgl: WebGLContext, drawCount: number, } const state: RenderableState = { visible: true, + pickable: false, depthMask: false } diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 729e7bb56c1b38207f8297e19a457212eec0cd27..d86ed125b4f6d796b02453c432a0017ebb9a89fc 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -262,8 +262,9 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { } - // get carbohydrate links induced by inter-unit bonds - // (e.g. for structures from the PDB archive __before__ carbohydrate remediation) + // get carbohydrate links induced by inter-unit bonds, that is + // terminal links plus inter monosaccharide links for structures from the + // PDB archive __before__ carbohydrate remediation for (let i = 0, il = structure.units.length; i < il; ++i) { const unit = structure.units[i] if (!Unit.isAtomic(unit)) continue @@ -309,10 +310,10 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { }) } - return { links, terminalLinks, elements, partialElements, ...buildLookups(elements, links) } + return { links, terminalLinks, elements, partialElements, ...buildLookups(elements, links, terminalLinks) } } -function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[]) { +function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[], terminalLinks: CarbohydrateTerminalLink[]) { // element lookup function elementKey(unit: Unit, anomericCarbon: ElementIndex) { @@ -347,6 +348,27 @@ function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[ return linkMap.get(linkKey(unitA, anomericCarbonA, unitB, anomericCarbonB)) } + // terminal link lookup + + function terminalLinkKey(unitA: Unit, elementA: ElementIndex, unitB: Unit, elementB: ElementIndex) { + return `${unitA.id}|${elementA}|${unitB.id}|${elementB}` + } + + const terminalLinkMap = new Map<string, number>() + for (let i = 0, il = terminalLinks.length; i < il; ++i) { + const { fromCarbohydrate, carbohydrateIndex, elementUnit, elementIndex } = terminalLinks[i] + const { unit, anomericCarbon } = elements[carbohydrateIndex] + if (fromCarbohydrate) { + terminalLinkMap.set(terminalLinkKey(unit, anomericCarbon, elementUnit, elementUnit.elements[elementIndex]), i) + } else { + terminalLinkMap.set(terminalLinkKey(elementUnit, elementUnit.elements[elementIndex], unit, anomericCarbon), i) + } + } + + function getTerminalLinkIndex(unitA: Unit, elementA: ElementIndex, unitB: Unit, elementB: ElementIndex) { + return terminalLinkMap.get(terminalLinkKey(unitA, elementA, unitB, elementB)) + } + // anomeric carbon lookup function anomericCarbonKey(unit: Unit, residueIndex: ResidueIndex) { @@ -364,5 +386,5 @@ function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[ return anomericCarbonMap.get(anomericCarbonKey(unit, residueIndex)) } - return { getElementIndex, getLinkIndex, getAnomericCarbon } + return { getElementIndex, getLinkIndex, getTerminalLinkIndex, getAnomericCarbon } } \ No newline at end of file diff --git a/src/mol-model/structure/structure/carbohydrates/data.ts b/src/mol-model/structure/structure/carbohydrates/data.ts index 0a8d383755bac79c9ffde4be7de61a4b8f331cf0..7743bd0ba2ab580cd9a5226c07bd0678253953af 100644 --- a/src/mol-model/structure/structure/carbohydrates/data.ts +++ b/src/mol-model/structure/structure/carbohydrates/data.ts @@ -8,6 +8,7 @@ import Unit from '../unit'; import { Vec3 } from 'mol-math/linear-algebra'; import { ResidueIndex, ElementIndex } from '../../model'; import { SaccharideComponent } from './constants'; +import StructureElement from '../element'; export interface CarbohydrateLink { readonly carbohydrateIndexA: number @@ -16,7 +17,7 @@ export interface CarbohydrateLink { export interface CarbohydrateTerminalLink { readonly carbohydrateIndex: number - readonly elementIndex: number + readonly elementIndex: StructureElement.UnitIndex readonly elementUnit: Unit /** specifies direction of the link */ readonly fromCarbohydrate: boolean @@ -31,7 +32,7 @@ export interface CarbohydrateElement { readonly ringAltId: string, } -// partial carbohydrate with no ring present +/** partial carbohydrate with no ring present */ export interface PartialCarbohydrateElement { readonly unit: Unit.Atomic, readonly residueIndex: ResidueIndex, @@ -45,5 +46,6 @@ export interface Carbohydrates { partialElements: ReadonlyArray<PartialCarbohydrateElement> getElementIndex: (unit: Unit, anomericCarbon: ElementIndex) => number | undefined getLinkIndex: (unitA: Unit, anomericCarbonA: ElementIndex, unitB: Unit, anomericCarbonB: ElementIndex) => number | undefined + getTerminalLinkIndex: (unitA: Unit, elementA: ElementIndex, unitB: Unit, elementB: ElementIndex) => number | undefined getAnomericCarbon: (unit: Unit, residueIndex: ResidueIndex) => ElementIndex | undefined } \ No newline at end of file diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index 2f2604382c0c1ac56da6b66f978b1200a21e2bcf..86c955cb0c2336f8e10b27e87406da2cf36ee82f 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -44,6 +44,7 @@ function controlFor(param: PD.Any): ParamControl | undefined { case 'converted': return ConvertedControl; case 'multi-select': return MultiSelectControl; case 'color': return ColorControl; + case 'vec3': return Vec3Control; case 'select': return SelectControl; case 'text': return TextControl; case 'interval': return IntervalControl; @@ -166,6 +167,17 @@ export class ColorControl extends SimpleParam<PD.Color> { } } +export class Vec3Control extends SimpleParam<PD.Vec3> { + // onChange = (e: React.ChangeEvent<HTMLSelectElement>) => { + // this.setState({ value: e.target.value }); + // this.props.onChange(e.target.value); + // } + + renderControl() { + return <span>vec3 TODO</span>; + } +} + export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiSelect<any>>, { isExpanded: boolean }> { state = { isExpanded: false } diff --git a/src/mol-repr/representation.ts b/src/mol-repr/representation.ts index 1325c1481edc2963cb4cff2584431022142599da..d271a2f35b28f501a3921444c79394c1f436e1c9 100644 --- a/src/mol-repr/representation.ts +++ b/src/mol-repr/representation.ts @@ -15,7 +15,7 @@ import { getQualityProps } from './util'; import { ColorTheme } from 'mol-theme/color'; import { SizeTheme } from 'mol-theme/size'; import { Theme, ThemeRegistryContext } from 'mol-theme/theme'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; // export interface RepresentationProps { // visuals?: string[] @@ -80,7 +80,7 @@ export interface RepresentationContext { export { Representation } interface Representation<D, P extends PD.Params = {}> { readonly label: string - readonly updated: BehaviorSubject<number> + readonly updated: Subject<number> readonly renderObjects: ReadonlyArray<RenderObject> readonly props: Readonly<PD.Values<P>> readonly params: Readonly<P> @@ -88,23 +88,26 @@ interface Representation<D, P extends PD.Params = {}> { getLoci: (pickingId: PickingId) => Loci mark: (loci: Loci, action: MarkerAction) => boolean setVisibility: (value: boolean) => void + setPickable: (value: boolean) => void destroy: () => void } namespace Representation { export type Any = Representation<any> export const Empty: Any = { - label: '', renderObjects: [], props: {}, params: {}, updated: new BehaviorSubject(0), + label: '', renderObjects: [], props: {}, params: {}, updated: new Subject(), createOrUpdate: () => Task.constant('', undefined), getLoci: () => EmptyLoci, mark: () => false, setVisibility: () => {}, + setPickable: () => {}, destroy: () => {} } export type Def<D, P extends PD.Params = {}> = { [k: string]: (getParams: RepresentationParamsGetter<D, P>) => Representation<any, P> } export function createMulti<D, P extends PD.Params = {}>(label: string, getParams: RepresentationParamsGetter<D, P>, reprDefs: Def<D, P>): Representation<D, P> { - const updated = new BehaviorSubject(0) + let version = 0 + const updated = new Subject<number>() let currentParams: P let currentProps: PD.Values<P> @@ -153,7 +156,7 @@ namespace Representation { await reprList[i].createOrUpdate(ctx, currentProps, currentData).runInContext(runtime) } } - updated.next(updated.getValue() + 1) + updated.next(version++) }) }, getLoci: (pickingId: PickingId) => { @@ -175,6 +178,11 @@ namespace Representation { reprList[i].setVisibility(value) } }, + setPickable: (value: boolean) => { + for (let i = 0, il = reprList.length; i < il; ++i) { + reprList[i].setPickable(value) + } + }, destroy() { for (let i = 0, il = reprList.length; i < il; ++i) { reprList[i].destroy() @@ -197,5 +205,6 @@ export interface Visual<D, P extends PD.Params> { getLoci: (pickingId: PickingId) => Loci mark: (loci: Loci, action: MarkerAction) => boolean setVisibility: (value: boolean) => void + setPickable: (value: boolean) => void destroy: () => void } \ No newline at end of file diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 27c7d9e7339a29438bce609f766c8cc2d8bf3570..c8e7ac8db60ea143a510f4358cf3b153cc233f6c 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -19,7 +19,7 @@ import { PickingId } from 'mol-geo/geometry/picking'; import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data'; import { LocationIterator } from 'mol-geo/util/location-iterator'; import { createTheme } from 'mol-theme/theme'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; export interface ShapeRepresentation<P extends ShapeParams> extends Representation<Shape, P> { } @@ -31,7 +31,8 @@ export const ShapeParams = { export type ShapeParams = typeof ShapeParams export function ShapeRepresentation<P extends ShapeParams>(): ShapeRepresentation<P> { - const updated = new BehaviorSubject(0) + let version = 0 + const updated = new Subject<number>() const renderObjects: RenderObject[] = [] let _renderObject: MeshRenderObject | undefined let _shape: Shape @@ -57,7 +58,7 @@ export function ShapeRepresentation<P extends ShapeParams>(): ShapeRepresentatio _renderObject = createMeshRenderObject(values, state) renderObjects.push(_renderObject) - updated.next(updated.getValue() + 1) + updated.next(version++) }); } @@ -103,6 +104,9 @@ export function ShapeRepresentation<P extends ShapeParams>(): ShapeRepresentatio setVisibility(value: boolean) { renderObjects.forEach(ro => ro.state.visible = value) }, + setPickable(value: boolean) { + renderObjects.forEach(ro => ro.state.pickable = value) + }, destroy() { // TODO renderObjects.length = 0 diff --git a/src/mol-repr/structure/complex-representation.ts b/src/mol-repr/structure/complex-representation.ts index f74d0551005db1201cd232d17b9b23321ade0838..816f84d4ddb0d95897f6930695a1fb6d22a23b35 100644 --- a/src/mol-repr/structure/complex-representation.ts +++ b/src/mol-repr/structure/complex-representation.ts @@ -15,10 +15,11 @@ import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { RepresentationContext, RepresentationParamsGetter } from 'mol-repr/representation'; import { Theme, createTheme } from 'mol-theme/theme'; import { ParamDefinition as PD } from 'mol-util/param-definition'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; export function ComplexRepresentation<P extends StructureParams>(label: string, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> { - const updated = new BehaviorSubject(0) + let version = 0 + const updated = new Subject<number>() let visual: ComplexVisual<P> | undefined let _structure: Structure @@ -38,7 +39,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, return Task.create('Creating or updating ComplexRepresentation', async runtime => { if (!visual) visual = visualCtor() await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, structure) - updated.next(updated.getValue() + 1) + updated.next(version++) }); } @@ -54,6 +55,10 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, if (visual) visual.setVisibility(value) } + function setPickable(value: boolean) { + if (visual) visual.setPickable(value) + } + function destroy() { if (visual) visual.destroy() } @@ -70,6 +75,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, getLoci, mark, setVisibility, + setPickable, destroy } } \ No newline at end of file diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index 25823c82f05c06438cb4c61af328390ad3a87395..ffccd126bec7138a427b8cdeb6c739f4ea7681c6 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -165,6 +165,9 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo setVisibility(value: boolean) { if (renderObject) renderObject.state.visible = value }, + setPickable(value: boolean) { + if (renderObject) renderObject.state.pickable = value + }, destroy() { // TODO renderObject = undefined @@ -187,7 +190,7 @@ export function ComplexMeshVisual<P extends ComplexMeshParams>(builder: ComplexM ...builder, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme) => { builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme) - if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true + if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true }, createEmptyGeometry: Mesh.createEmpty, createRenderObject: createComplexMeshRenderObject, diff --git a/src/mol-repr/structure/representation/carbohydrate.ts b/src/mol-repr/structure/representation/carbohydrate.ts index 82660da3fb1f0eba58ad787a8843cca7684450bb..ac2e0d87bd53066ddf807ea3b39c2c2f73c3cc2f 100644 --- a/src/mol-repr/structure/representation/carbohydrate.ts +++ b/src/mol-repr/structure/representation/carbohydrate.ts @@ -6,6 +6,7 @@ import { CarbohydrateSymbolVisual, CarbohydrateSymbolParams } from '../visual/carbohydrate-symbol-mesh'; import { CarbohydrateLinkVisual, CarbohydrateLinkParams } from '../visual/carbohydrate-link-cylinder'; +import { CarbohydrateTerminalLinkParams, CarbohydrateTerminalLinkVisual } from '../visual/carbohydrate-terminal-link-cylinder'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { ComplexRepresentation } from '../complex-representation'; import { StructureRepresentation, StructureRepresentationProvider } from '../representation'; @@ -17,6 +18,7 @@ import { BuiltInColorThemeOptions, getBuiltInColorThemeParams } from 'mol-theme/ const CarbohydrateVisuals = { 'carbohydrate-symbol': (getParams: RepresentationParamsGetter<Structure, CarbohydrateSymbolParams>) => ComplexRepresentation('Carbohydrate symbol mesh', getParams, CarbohydrateSymbolVisual), 'carbohydrate-link': (getParams: RepresentationParamsGetter<Structure, CarbohydrateLinkParams>) => ComplexRepresentation('Carbohydrate link cylinder', getParams, CarbohydrateLinkVisual), + 'carbohydrate-terminal-link': (getParams: RepresentationParamsGetter<Structure, CarbohydrateTerminalLinkParams>) => ComplexRepresentation('Carbohydrate terminal link cylinder', getParams, CarbohydrateTerminalLinkVisual), } type CarbohydrateVisualName = keyof typeof CarbohydrateVisuals const CarbohydrateVisualOptions = Object.keys(CarbohydrateVisuals).map(name => [name, name] as [CarbohydrateVisualName, string]) @@ -24,8 +26,9 @@ const CarbohydrateVisualOptions = Object.keys(CarbohydrateVisuals).map(name => [ export const CarbohydrateParams = { ...CarbohydrateSymbolParams, ...CarbohydrateLinkParams, + ...CarbohydrateTerminalLinkParams, colorTheme: PD.Mapped('carbohydrate-symbol', BuiltInColorThemeOptions, getBuiltInColorThemeParams), - visuals: PD.MultiSelect<CarbohydrateVisualName>(['carbohydrate-symbol', 'carbohydrate-link'], CarbohydrateVisualOptions), + visuals: PD.MultiSelect<CarbohydrateVisualName>(['carbohydrate-symbol', 'carbohydrate-link', 'carbohydrate-terminal-link'], CarbohydrateVisualOptions), } PD.getDefaultValues(CarbohydrateParams).colorTheme.name export type CarbohydrateParams = typeof CarbohydrateParams diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index 42bb3fb8604b8660664dbdf7476772105c6cab63..84aed6c3de81b45c5dd44ff2b813e0b01d2e17dc 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -17,7 +17,7 @@ import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { Theme, createTheme } from 'mol-theme/theme'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { UnitKind, UnitKindOptions } from './visual/util/common'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; export const UnitsParams = { ...StructureParams, @@ -28,7 +28,8 @@ export type UnitsParams = typeof UnitsParams export interface UnitsVisual<P extends UnitsParams> extends Visual<StructureGroup, P> { } export function UnitsRepresentation<P extends UnitsParams>(label: string, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> { - const updated = new BehaviorSubject(0) + let version = 0 + const updated = new Subject<number>() let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>() let _structure: Structure @@ -120,7 +121,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar } } if (structure) _structure = structure - updated.next(updated.getValue() + 1) + updated.next(version++) }); } @@ -147,6 +148,12 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar }) } + function setPickable(value: boolean) { + visuals.forEach(({ visual }) => { + visual.setPickable(value) + }) + } + function destroy() { visuals.forEach(({ visual }) => visual.destroy()) visuals.clear() @@ -163,11 +170,12 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar }, get props() { return _props }, get params() { return _params }, - get updated() { return updated }, + updated, createOrUpdate, getLoci, mark, setVisibility, + setPickable, destroy } } \ No newline at end of file diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index f5cc5a4a58ce0ae94f8eb3bd551a47d0d49565f8..abb5dccb56234f38b41d0e16ff71d2c48953787c 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -195,6 +195,9 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB setVisibility(value: boolean) { if (renderObject) renderObject.state.visible = value }, + setPickable(value: boolean) { + if (renderObject) renderObject.state.pickable = value + }, destroy() { // TODO renderObject = undefined @@ -216,7 +219,7 @@ export function UnitsMeshVisual<P extends UnitsMeshParams>(builder: UnitsMeshVis ...builder, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme) => { builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme) - if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true + if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true }, createEmptyGeometry: Mesh.createEmpty, createRenderObject: createUnitsMeshRenderObject, @@ -240,7 +243,7 @@ export function UnitsPointsVisual<P extends UnitsPointsParams>(builder: UnitsPoi createRenderObject: createUnitsPointsRenderObject, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme) => { builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme) - if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true + if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true }, updateValues: Points.updateValues }) @@ -262,7 +265,7 @@ export function UnitsLinesVisual<P extends UnitsLinesParams>(builder: UnitsLines createRenderObject: createUnitsLinesRenderObject, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme) => { builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme) - if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true + if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true }, updateValues: Lines.updateValues }) @@ -284,7 +287,7 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeParams>(build createRenderObject: createUnitsDirectVolumeRenderObject, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme) => { builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme) - if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true + if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true }, updateValues: DirectVolume.updateValues }) diff --git a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts index e58322f57d5b6f1eb90db7832db26dbe61007433..05d5a04b0d66afd2c4bf649fb66826835538fe13 100644 --- a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts @@ -7,7 +7,7 @@ import { Structure, Link, StructureElement } from 'mol-model/structure'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { Vec3 } from 'mol-math/linear-algebra'; -import { createLinkCylinderMesh, LinkCylinderProps, LinkCylinderParams } from './util/link'; +import { createLinkCylinderMesh, LinkCylinderParams } from './util/link'; import { OrderedSet, Interval } from 'mol-data/int'; import { ComplexMeshVisual, ComplexVisual } from '../complex-visual'; import { LinkType } from 'mol-model/structure/model/types'; @@ -21,22 +21,10 @@ import { VisualUpdateState } from '../../util'; import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; -// TODO create seperate visual -// for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) { -// const tl = carbohydrates.terminalLinks[i] -// const center = carbohydrates.elements[tl.carbohydrateIndex].geometry.center -// tl.elementUnit.conformation.position(tl.elementUnit.elements[tl.elementIndex], p) -// if (tl.fromCarbohydrate) { -// builder.addCylinder(center, p, 0.5, linkParams) -// } else { -// builder.addCylinder(p, center, 0.5, linkParams) -// } -// } - -const radiusFactor = 0.3 - -async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) { +async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateLinkParams>, mesh?: Mesh) { const { links, elements } = structure.carbohydrates + const { linkSizeFactor } = props + const location = StructureElement.create() const builderProps = { @@ -53,7 +41,7 @@ async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: const l = links[edgeIndex] location.unit = elements[l.carbohydrateIndexA].unit location.element = elements[l.carbohydrateIndexA].anomericCarbon - return theme.size.size(location) * radiusFactor + return theme.size.size(location) * linkSizeFactor } } @@ -64,6 +52,7 @@ export const CarbohydrateLinkParams = { ...UnitsMeshParams, ...LinkCylinderParams, detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), + linkSizeFactor: PD.Numeric(0.3, { min: 0, max: 3, step: 0.01 }), } export type CarbohydrateLinkParams = typeof CarbohydrateLinkParams diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts index 440d6e834e948a8b2ae6de8cdf85855686316368..7a5acbf598fc3d6b1241632712d95c97ee30358e 100644 --- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts @@ -30,8 +30,7 @@ const t = Mat4.identity() const sVec = Vec3.zero() const pd = Vec3.zero() -const sideFactor = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4) -const radiusFactor = 1.75 +const SideFactor = 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4) const box = Box() const perforatedBox = PerforatedBox() @@ -47,7 +46,7 @@ const hexagonalPrism = HexagonalPrism() async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateSymbolParams>, mesh?: Mesh) { const builder = MeshBuilder.create(256, 128, mesh) - const { detail } = props + const { detail, sizeFactor } = props const carbohydrates = structure.carbohydrates const n = carbohydrates.elements.length @@ -60,8 +59,8 @@ async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Struc l.unit = c.unit l.element = c.unit.elements[c.anomericCarbon] const size = theme.size.size(l) - const radius = size * radiusFactor - const side = size * sideFactor + const radius = size * sizeFactor + const side = size * sizeFactor * SideFactor const { center, normal, direction } = c.geometry Vec3.add(pd, center, direction) @@ -148,6 +147,7 @@ async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Struc export const CarbohydrateSymbolParams = { ...ComplexMeshParams, detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), + sizeFactor: PD.Numeric(1.75, { min: 0, max: 10, step: 0.01 }), } export type CarbohydrateSymbolParams = typeof CarbohydrateSymbolParams diff --git a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d594fd788a192d1fa5fb1df4e2ec6f2bff9ca65 --- /dev/null +++ b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Structure, Link, StructureElement } from 'mol-model/structure'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { createLinkCylinderMesh, LinkCylinderParams } from './util/link'; +import { OrderedSet, Interval } from 'mol-data/int'; +import { ComplexMeshVisual, ComplexVisual } from '../complex-visual'; +import { LinkType } from 'mol-model/structure/model/types'; +import { BitFlags } from 'mol-util'; +import { UnitsMeshParams } from '../units-visual'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { Mesh } from 'mol-geo/geometry/mesh/mesh'; +import { LocationIterator } from 'mol-geo/util/location-iterator'; +import { PickingId } from 'mol-geo/geometry/picking'; +import { VisualUpdateState } from '../../util'; +import { VisualContext } from 'mol-repr/representation'; +import { Theme } from 'mol-theme/theme'; + +async function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateTerminalLinkParams>, mesh?: Mesh) { + const { terminalLinks, elements } = structure.carbohydrates + const { linkSizeFactor } = props + + const location = StructureElement.create() + + const builderProps = { + linkCount: terminalLinks.length, + referencePosition: (edgeIndex: number) => null, + position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { + const l = terminalLinks[edgeIndex] + if (l.fromCarbohydrate) { + Vec3.copy(posA, elements[l.carbohydrateIndex].geometry.center) + l.elementUnit.conformation.position(l.elementIndex, posB) + } else { + l.elementUnit.conformation.position(l.elementIndex, posA) + Vec3.copy(posB, elements[l.carbohydrateIndex].geometry.center) + } + }, + order: (edgeIndex: number) => 1, + flags: (edgeIndex: number) => BitFlags.create(LinkType.Flag.None), + radius: (edgeIndex: number) => { + const l = terminalLinks[edgeIndex] + if (l.fromCarbohydrate) { + location.unit = elements[l.carbohydrateIndex].unit + location.element = elements[l.carbohydrateIndex].anomericCarbon + } else { + location.unit = l.elementUnit + location.element = l.elementUnit.elements[l.elementIndex] + } + return theme.size.size(location) * linkSizeFactor + } + } + + return createLinkCylinderMesh(ctx, builderProps, props, mesh) +} + +export const CarbohydrateTerminalLinkParams = { + ...UnitsMeshParams, + ...LinkCylinderParams, + detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), + linkSizeFactor: PD.Numeric(0.3, { min: 0, max: 3, step: 0.01 }), +} +export type CarbohydrateTerminalLinkParams = typeof CarbohydrateTerminalLinkParams + +export function CarbohydrateTerminalLinkVisual(): ComplexVisual<CarbohydrateTerminalLinkParams> { + return ComplexMeshVisual<CarbohydrateTerminalLinkParams>({ + defaultProps: PD.getDefaultValues(CarbohydrateTerminalLinkParams), + createGeometry: createCarbohydrateTerminalLinkCylinderMesh, + createLocationIterator: CarbohydrateTerminalLinkIterator, + getLoci: getTerminalLinkLoci, + mark: markTerminalLink, + setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CarbohydrateTerminalLinkParams>, currentProps: PD.Values<CarbohydrateTerminalLinkParams>) => { + state.createGeometry = newProps.radialSegments !== currentProps.radialSegments + } + }) +} + +function CarbohydrateTerminalLinkIterator(structure: Structure): LocationIterator { + const { elements, terminalLinks } = structure.carbohydrates + const groupCount = terminalLinks.length + const instanceCount = 1 + const location = Link.Location() + const getLocation = (groupIndex: number) => { + const terminalLink = terminalLinks[groupIndex] + const carb = elements[terminalLink.carbohydrateIndex] + const indexCarb = OrderedSet.indexOf(carb.unit.elements, carb.anomericCarbon) + if (terminalLink.fromCarbohydrate) { + location.aUnit = carb.unit + location.aIndex = indexCarb as StructureElement.UnitIndex + location.bUnit = terminalLink.elementUnit + location.bIndex = terminalLink.elementIndex + } else { + location.aUnit = terminalLink.elementUnit + location.aIndex = terminalLink.elementIndex + location.bUnit = carb.unit + location.bIndex = indexCarb as StructureElement.UnitIndex + } + return location + } + return LocationIterator(groupCount, instanceCount, getLocation, true) +} + +function getTerminalLinkLoci(pickingId: PickingId, structure: Structure, id: number) { + const { objectId, groupId } = pickingId + if (id === objectId) { + const { terminalLinks, elements } = structure.carbohydrates + const l = terminalLinks[groupId] + const carb = elements[l.carbohydrateIndex] + const carbIndex = OrderedSet.indexOf(carb.unit.elements, carb.anomericCarbon) + + if (l.fromCarbohydrate) { + return Link.Loci(structure, [ + Link.Location( + carb.unit, carbIndex as StructureElement.UnitIndex, + l.elementUnit, l.elementIndex + ) + ]) + } else { + return Link.Loci(structure, [ + Link.Location( + l.elementUnit, l.elementIndex, + carb.unit, carbIndex as StructureElement.UnitIndex + ) + ]) + } + } + return EmptyLoci +} + +function markTerminalLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { + const { getTerminalLinkIndex } = structure.carbohydrates + + let changed = false + if (Link.isLoci(loci)) { + for (const l of loci.links) { + const idx = getTerminalLinkIndex(l.aUnit, l.aUnit.elements[l.aIndex], l.bUnit, l.bUnit.elements[l.bIndex]) + if (idx !== undefined) { + if (apply(Interval.ofSingleton(idx))) changed = true + } + } + } + return changed +} \ No newline at end of file diff --git a/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts b/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts index 31e82c69c78b9f1e5bad8b5022b02b3a2f7e7e8c..f85233d3541260ded2037f1c37c814e17e00be24 100644 --- a/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts +++ b/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts @@ -7,7 +7,7 @@ import { Link, Structure, StructureElement } from 'mol-model/structure'; import { ComplexVisual } from '../representation'; import { VisualUpdateState } from '../../util'; -import { LinkCylinderProps, createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util/link'; +import { createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util/link'; import { Vec3 } from 'mol-math/linear-algebra'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { ComplexMeshVisual, ComplexMeshParams } from '../complex-visual'; @@ -19,9 +19,10 @@ import { PickingId } from 'mol-geo/geometry/picking'; import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; -async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) { +async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitLinkParams>, mesh?: Mesh) { const links = structure.links const { bondCount, bonds } = links + const { sizeFactor } = props if (!bondCount) return Mesh.createEmpty(mesh) @@ -42,7 +43,7 @@ async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: St const b = bonds[edgeIndex] location.unit = b.unitA location.element = b.unitA.elements[b.indexA] - return theme.size.size(location) + return theme.size.size(location) * sizeFactor } } @@ -52,6 +53,7 @@ async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: St export const InterUnitLinkParams = { ...ComplexMeshParams, ...LinkCylinderParams, + sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), } export type InterUnitLinkParams = typeof InterUnitLinkParams diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index 12e24cdf1cd98895bb3e61971202c9aea2a1b5b8..075c02c8a417f4671251242aeac212d5387d6132 100644 --- a/src/mol-repr/volume/representation.ts +++ b/src/mol-repr/volume/representation.ts @@ -20,7 +20,7 @@ import { NullLocation } from 'mol-model/location'; import { VisualUpdateState } from 'mol-repr/util'; import { ValueCell } from 'mol-util'; import { Theme, createTheme } from 'mol-theme/theme'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeData, P> { } @@ -39,7 +39,7 @@ interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry updateValues(values: RenderableValues, newProps: PD.Values<P>): void } -export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeometryBuilder<P, Geometry>) { +export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeometryBuilder<P, Geometry>): VolumeVisual<P> { const { defaultProps, createGeometry, getLoci, mark, setUpdateState } = builder const { createRenderObject, updateValues } = builder const updateState = VisualUpdateState.create() @@ -120,6 +120,9 @@ export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeomet setVisibility(value: boolean) { if (renderObject) renderObject.state.visible = value }, + setPickable(value: boolean) { + if (renderObject) renderObject.state.pickable = value + }, destroy() { // TODO renderObject = undefined @@ -140,7 +143,8 @@ export const VolumeParams = { export type VolumeParams = typeof VolumeParams export function VolumeRepresentation<P extends VolumeParams>(label: string, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: (volume: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> { - const updated = new BehaviorSubject(0) + let version = 0 + const updated = new Subject<number>() let visual: VolumeVisual<P> let _volume: VolumeData @@ -174,7 +178,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, getP await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volume) busy = false } - updated.next(updated.getValue() + 1) + updated.next(version++) }); } @@ -194,6 +198,10 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, getP if (visual) visual.setVisibility(value) } + function setPickable(value: boolean) { + if (visual) visual.setPickable(value) + } + return { label, get renderObjects() { @@ -206,6 +214,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, getP getLoci, mark, setVisibility, + setPickable, destroy } } \ No newline at end of file diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 2c1922a953cad32e68a35fea67d6719389d5826b..8bb45df5b470cdb53cbe3acb8d691de78ed21b92 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -7,7 +7,7 @@ import { Color as ColorData } from './color'; import { shallowEqual } from 'mol-util'; -import { Vec2 } from 'mol-math/linear-algebra'; +import { Vec2 as Vec2Data, Vec3 as Vec3Data } from 'mol-math/linear-algebra'; import { deepClone } from './object'; export namespace ParamDefinition { @@ -75,6 +75,13 @@ export namespace ParamDefinition { return setInfo<Color>({ type: 'color', defaultValue }, info) } + export interface Vec3 extends Base<Vec3Data> { + type: 'vec3' + } + export function Vec3(defaultValue: Vec3Data, info?: Info): Vec3 { + return setInfo<Vec3>({ type: 'vec3', defaultValue }, info) + } + export interface Range { /** If given treat as a range. */ min?: number @@ -108,10 +115,10 @@ export namespace ParamDefinition { return setInfo<Interval>(setRange({ type: 'interval', defaultValue }, range), info) } - export interface LineGraph extends Base<Vec2[]> { + export interface LineGraph extends Base<Vec2Data[]> { type: 'line-graph' } - export function LineGraph(defaultValue: Vec2[], info?: Info): LineGraph { + export function LineGraph(defaultValue: Vec2Data[], info?: Info): LineGraph { return setInfo<LineGraph>({ type: 'line-graph', defaultValue }, info) } @@ -149,7 +156,7 @@ export namespace ParamDefinition { return { type: 'converted', defaultValue: toValue(converted.defaultValue), converted, fromValue, toValue }; } - export type Any = Value<any> | Select<any> | MultiSelect<any> | Boolean | Text | Color | Numeric | Interval | LineGraph | Group<any> | Mapped<any> | Converted<any, any> + export type Any = Value<any> | Select<any> | MultiSelect<any> | Boolean | Text | Color | Vec3 | Numeric | Interval | LineGraph | Group<any> | Mapped<any> | Converted<any, any> export type Params = { [k: string]: Any } export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] } @@ -210,9 +217,11 @@ export namespace ParamDefinition { const u = a as LineGraph['defaultValue'], v = b as LineGraph['defaultValue']; if (u.length !== v.length) return false; for (let i = 0, _i = u.length; i < _i; i++) { - if (!Vec2.areEqual(u[i], v[i])) return false; + if (!Vec2Data.areEqual(u[i], v[i])) return false; } return true; + } else if (p.type === 'vec3') { + return Vec3Data.equals(a, b); } else if (typeof a === 'object' && typeof b === 'object') { return shallowEqual(a, b); }