diff --git a/src/mol-app/ui/entity/tree.tsx b/src/mol-app/ui/entity/tree.tsx index 8c1612cd1a3440f1b05e2eb26b29893fef268960..d83e0a9da6d726f363dddb5e52820e71251fd2df 100644 --- a/src/mol-app/ui/entity/tree.tsx +++ b/src/mol-app/ui/entity/tree.tsx @@ -13,7 +13,7 @@ import { View } from '../view'; import { EntityTreeController } from '../../controller/entity/tree'; import { Controller } from '../../controller/controller'; import { AnyEntity, RootEntity } from 'mol-view/state/entity'; -import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBallAndStick, DistanceRestraintUpdate, CartoonUpdate, BallAndStickUpdate, BackboneUpdate, MmcifUrlToSpacefill } from 'mol-view/state/transform'; +import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBallAndStick, DistanceRestraintUpdate, CartoonUpdate, BallAndStickUpdate, BackboneUpdate, MmcifUrlToSpacefill, CarbohydrateUpdate } from 'mol-view/state/transform'; function getTransforms(entity: AnyEntity): AnyTransform[] { const transforms: AnyTransform[] = [] @@ -57,6 +57,9 @@ function getTransforms(entity: AnyEntity): AnyTransform[] { case 'cartoon': transforms.push(CartoonUpdate) break; + case 'carbohydrate': + transforms.push(CarbohydrateUpdate) + break; } return transforms } diff --git a/src/mol-app/ui/transform/backbone.tsx b/src/mol-app/ui/transform/backbone.tsx index f5c3f072cb21f97a225ad8b7cc1dfad5cc039b89..4e6569669eec3ca20fcc027ee25012f5351801d8 100644 --- a/src/mol-app/ui/transform/backbone.tsx +++ b/src/mol-app/ui/transform/backbone.tsx @@ -46,7 +46,7 @@ export class Backbone extends View<Controller<any>, BackboneState, { transform: detail: 2, colorTheme: { name: 'element-symbol' } as ColorThemeProps, colorValue: 0x000000, - sizeTheme: { name: 'uniform' } as SizeThemeProps, + sizeTheme: { name: 'uniform', factor: 1 } as SizeThemeProps, visible: true, alpha: 1, depthMask: true, @@ -219,6 +219,21 @@ export class Backbone extends View<Controller<any>, BackboneState, { transform: /> </div> </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.sizeTheme.factor || 1} + label='Size factor' + min={0.1} + max={3} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ + sizeTheme: { ...this.state.sizeTheme, factor: value } + })} + /> + </div> + </div> </div> </div> </div> diff --git a/src/mol-app/ui/transform/ball-and-stick.tsx b/src/mol-app/ui/transform/ball-and-stick.tsx index 382758903a956f27055cabc6d0537fa00c98ad3c..92363009ad67d89d441e71cf017576d82a507569 100644 --- a/src/mol-app/ui/transform/ball-and-stick.tsx +++ b/src/mol-app/ui/transform/ball-and-stick.tsx @@ -49,7 +49,7 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra flatShaded: false, colorTheme: { name: 'element-symbol' } as ColorThemeProps, colorValue: 0x000000, - sizeTheme: { name: 'uniform' } as SizeThemeProps, + sizeTheme: { name: 'uniform', value: 0.15 } as SizeThemeProps, visible: true, alpha: 1, depthMask: true, @@ -211,6 +211,21 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra /> </div> </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.sizeTheme.factor || 1} + label='Size factor' + min={0.1} + max={3} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ + sizeTheme: { ...this.state.sizeTheme, factor: value } + })} + /> + </div> + </div> </div> </div> </div> diff --git a/src/mol-app/ui/transform/carbohydrate.tsx b/src/mol-app/ui/transform/carbohydrate.tsx index 34392b1f1ff4d137f6b1c5a9f1e0c8fa13239cee..704c446bc4ad2813e954cc4f8628945152e419cf 100644 --- a/src/mol-app/ui/transform/carbohydrate.tsx +++ b/src/mol-app/ui/transform/carbohydrate.tsx @@ -50,7 +50,7 @@ export class Carbohydrate extends View<Controller<any>, CarbohydrateState, { tra detail: 2, colorTheme: { name: 'element-symbol' } as ColorThemeProps, colorValue: 0x000000, - sizeTheme: { name: 'uniform' } as SizeThemeProps, + sizeTheme: { name: 'uniform', factor: 1 } as SizeThemeProps, visible: true, alpha: 1, depthMask: true, @@ -227,6 +227,21 @@ export class Carbohydrate extends View<Controller<any>, CarbohydrateState, { tra /> </div> </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.sizeTheme.factor || 1} + label='Size factor' + min={0.1} + max={3} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ + sizeTheme: { ...this.state.sizeTheme, factor: value } + })} + /> + </div> + </div> </div> </div> </div> diff --git a/src/mol-app/ui/transform/cartoon.tsx b/src/mol-app/ui/transform/cartoon.tsx index 4b5e2cb8fe530abd40d0bc0cbebaffd2dbdc6dad..7c9ee2b2984acee8c1efe734de996e231e1b53cd 100644 --- a/src/mol-app/ui/transform/cartoon.tsx +++ b/src/mol-app/ui/transform/cartoon.tsx @@ -36,6 +36,10 @@ interface CartoonState { useFog: boolean quality: VisualQuality unitKinds: Unit.Kind[] + linearSegments: number + radialSegments: number + aspectRatio: number + arrowFactor: number } export class Cartoon extends View<Controller<any>, CartoonState, { transform: CartoonUpdate, entity: CartoonEntity, ctx: StateContext }> { @@ -46,13 +50,17 @@ export class Cartoon extends View<Controller<any>, CartoonState, { transform: Ca detail: 2, colorTheme: { name: 'element-symbol' } as ColorThemeProps, colorValue: 0x000000, - sizeTheme: { name: 'uniform' } as SizeThemeProps, + sizeTheme: { name: 'uniform', value: 0.13, factor: 1 } as SizeThemeProps, visible: true, alpha: 1, depthMask: true, useFog: true, quality: 'auto' as VisualQuality, - unitKinds: [] as Unit.Kind[] + unitKinds: [] as Unit.Kind[], + linearSegments: 8, + radialSegments: 12, + aspectRatio: 8, + arrowFactor: 1.5 } componentWillMount() { @@ -219,6 +227,49 @@ export class Cartoon extends View<Controller<any>, CartoonState, { transform: Ca /> </div> </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.aspectRatio || 1} + label='Aspect ratio' + min={0.1} + max={10} + step={0.1} + callOnChangeWhileSliding={true} + onChange={value => this.update({ aspectRatio: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.sizeTheme.value || 0.1} + label='Size value' + min={0.01} + max={0.3} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ + sizeTheme: { ...this.state.sizeTheme, value: value } + })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.sizeTheme.factor || 1} + label='Size factor' + min={0.1} + max={3} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ + sizeTheme: { ...this.state.sizeTheme, factor: value } + })} + /> + </div> + </div> </div> </div> </div> diff --git a/src/mol-app/ui/transform/list.tsx b/src/mol-app/ui/transform/list.tsx index 2c09db8a620a3e347924d93dda2c99da2b645034..f6f72b804a99ac43535e537fa98491f793bbff55 100644 --- a/src/mol-app/ui/transform/list.tsx +++ b/src/mol-app/ui/transform/list.tsx @@ -23,6 +23,7 @@ import { Cartoon } from './cartoon'; import { DistanceRestraint } from './distance-restraint'; import { Backbone } from './backbone'; import { UrlLoader } from './url-loader'; +import { Carbohydrate } from './carbohydrate'; function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) { switch (transform.kind) { @@ -44,6 +45,8 @@ function getTransformComponent(controller: TransformListController, entity: AnyE return <Backbone controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Backbone> case 'cartoon-update': return <Cartoon controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Cartoon> + case 'carbohydrate-update': + return <Carbohydrate controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Carbohydrate> } return <Transform controller={controller} entity={entity} transform={transform}></Transform> } diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts index 5421bf4e01e051f11752c0baa3d431005b20bd57..a315ba72dbc38bc18bc8ea4116f4ca100e02155e 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts @@ -16,7 +16,9 @@ import { LocationIterator } from './util/location-iterator'; import { createLinkCylinderMesh, DefaultLinkCylinderProps, LinkCylinderProps } from './util/link'; import { OrderedSet, Interval } from 'mol-data/int'; import { ComplexMeshVisual } from '../complex-visual'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; +import { LinkType } from 'mol-model/structure/model/types'; +import { BitFlags } from 'mol-util'; // TODO create seperate visual // for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) { @@ -30,8 +32,12 @@ import { SizeThemeProps } from 'mol-view/theme/size'; // } // } +const radiusFactor = 0.3 + async function createCarbohydrateLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) { const { links, elements } = structure.carbohydrates + const sizeTheme = SizeTheme(props.sizeTheme) + const location = StructureElement.create() const builderProps = { linkCount: links.length, @@ -42,7 +48,13 @@ async function createCarbohydrateLinkCylinderMesh(ctx: RuntimeContext, structure Vec3.copy(posB, elements[l.carbohydrateIndexB].geometry.center) }, order: (edgeIndex: number) => 1, - flags: (edgeIndex: number) => 0 + flags: (edgeIndex: number) => BitFlags.create(LinkType.Flag.None), + radius: (edgeIndex: number) => { + const l = links[edgeIndex] + location.unit = elements[l.carbohydrateIndexA].unit + location.element = elements[l.carbohydrateIndexA].anomericCarbon + return sizeTheme.size(location) * radiusFactor + } } return createLinkCylinderMesh(ctx, builderProps, props, mesh) diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts index 7186ca7ae35d9922b099a6822e7fce52a3620a7c..142791e62b0ad390e04ce01df6aee8b0a25f4a7b 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -16,24 +16,34 @@ import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/struct import { LocationIterator } from './util/location-iterator'; import { OrderedSet, Interval } from 'mol-data/int'; import { ComplexMeshVisual, DefaultComplexMeshProps } from '../complex-visual'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; 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 + async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, props: CarbohydrateSymbolProps, mesh?: Mesh) { const builder = MeshBuilder.create(256, 128, mesh) - const carbohydrates = structure.carbohydrates + const sizeTheme = SizeTheme(props.sizeTheme) + const { detail } = props - const side = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4) - const radius = 1.75 + const carbohydrates = structure.carbohydrates + const l = StructureElement.create() for (let i = 0, il = carbohydrates.elements.length; i < il; ++i) { const c = carbohydrates.elements[i]; const shapeType = getSaccharideShape(c.component.type) + l.unit = c.unit + l.element = c.unit.elements[c.anomericCarbon] + const size = sizeTheme.size(l) + const radius = size * radiusFactor + const side = size * sideFactor + const { center, normal, direction } = c.geometry Vec3.add(pd, center, direction) Mat4.targetTo(t, center, pd, normal) @@ -43,7 +53,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru switch (shapeType) { case SaccharideShapes.FilledSphere: - builder.addSphere(center, radius, 2) + builder.addSphere(center, radius, detail) break; case SaccharideShapes.FilledCube: Mat4.scaleUniformly(t, t, side) @@ -113,7 +123,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru export const DefaultCarbohydrateSymbolProps = { ...DefaultComplexMeshProps, - sizeTheme: { name: 'physical', factor: 1 } as SizeThemeProps, + sizeTheme: { name: 'uniform', value: 1, factor: 1 } as SizeThemeProps, detail: 0, unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[] } diff --git a/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts b/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts index b16bd15d70011fb9d0c8e61de8a7637a2fe7fcd8..6930ff97c4226f37b297e587406ede6f383f3b76 100644 --- a/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts @@ -15,26 +15,35 @@ import { Loci, EmptyLoci } from 'mol-model/loci'; import { ComplexMeshVisual, DefaultComplexMeshProps } from '../complex-visual'; import { LocationIterator } from './util/location-iterator'; import { Interval } from 'mol-data/int'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; +import { BitFlags } from 'mol-util'; +import { LinkType } from 'mol-model/structure/model/types'; async function createCrossLinkRestraintCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) { const crossLinks = structure.crossLinkRestraints if (!crossLinks.count) return Mesh.createEmpty(mesh) + const sizeTheme = SizeTheme(props.sizeTheme) + const location = StructureElement.create() + const builderProps = { linkCount: crossLinks.count, referencePosition: (edgeIndex: number) => null, position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { const b = crossLinks.pairs[edgeIndex] - // console.log(b) const uA = b.unitA, uB = b.unitB uA.conformation.position(uA.elements[b.indexA], posA) uB.conformation.position(uB.elements[b.indexB], posB) - // console.log(posA, posB) }, order: (edgeIndex: number) => 1, - flags: (edgeIndex: number) => 0 + flags: (edgeIndex: number) => BitFlags.create(LinkType.Flag.None), + radius: (edgeIndex: number) => { + const b = crossLinks.pairs[edgeIndex] + location.unit = b.unitA + location.element = b.unitA.elements[b.indexA] + return sizeTheme.size(location) + } } return createLinkCylinderMesh(ctx, builderProps, props, mesh) diff --git a/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts b/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts index 1e0e2ef0d04604d1d2253837fb979da117977e40..40d6c5709e25357a57971fc5195683f53437671b 100644 --- a/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts @@ -15,7 +15,8 @@ import { Loci, EmptyLoci } from 'mol-model/loci'; import { LinkIterator } from './util/location-iterator'; import { ComplexMeshVisual, DefaultComplexMeshProps } from '../complex-visual'; import { Interval } from 'mol-data/int'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; +import { BitFlags } from 'mol-util'; async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) { const links = structure.links @@ -23,6 +24,9 @@ async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: S if (!bondCount) return Mesh.createEmpty(mesh) + const sizeTheme = SizeTheme(props.sizeTheme) + const location = StructureElement.create() + const builderProps = { linkCount: bondCount, referencePosition: (edgeIndex: number) => null, // TODO @@ -33,7 +37,13 @@ async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: S uB.conformation.position(uB.elements[b.indexB], posB) }, order: (edgeIndex: number) => bonds[edgeIndex].order, - flags: (edgeIndex: number) => bonds[edgeIndex].flag + flags: (edgeIndex: number) => BitFlags.create(bonds[edgeIndex].flag), + radius: (edgeIndex: number) => { + const b = bonds[edgeIndex] + location.unit = b.unitA + location.element = b.unitA.elements[b.indexA] + return sizeTheme.size(location) + } } return createLinkCylinderMesh(ctx, builderProps, props, mesh) diff --git a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts index 0e702290fdb1be8491a90e0eb58bfd3d0f6c5a2d..12c1a7c0cc0734e417fc6cfabf1b9d5194e30a74 100644 --- a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts @@ -16,11 +16,15 @@ import { Loci, EmptyLoci } from 'mol-model/loci'; import { LinkIterator } from './util/location-iterator'; import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; import { Interval } from 'mol-data/int'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; +import { BitFlags } from 'mol-util'; async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) + const sizeTheme = SizeTheme(props.sizeTheme) + const location = StructureElement.create(unit) + const elements = unit.elements; const links = unit.links const { edgeCount, a, b, edgeProps, offset } = links @@ -49,7 +53,11 @@ async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, pos(elements[b[edgeIndex]], posB) }, order: (edgeIndex: number) => _order[edgeIndex], - flags: (edgeIndex: number) => _flags[edgeIndex] + flags: (edgeIndex: number) => BitFlags.create(_flags[edgeIndex]), + radius: (edgeIndex: number) => { + location.element = elements[a[edgeIndex]] + return sizeTheme.size(location) + } } return createLinkCylinderMesh(ctx, builderProps, props, mesh) diff --git a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts index 74a1d3841d0d8261e0c9c849e96121cf3aeb7c54..bf30fc790faeb7d84be074fdd6fba1956c54ed0f 100644 --- a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from 'mol-model/structure'; +import { Unit, StructureElement } from 'mol-model/structure'; import { UnitsVisual } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../shape/mesh'; @@ -14,31 +14,49 @@ import { getElementLoci, markElement } from './util/element'; import { Vec3 } from 'mol-math/linear-algebra'; import { StructureElementIterator } from './util/location-iterator'; import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; +import { CylinderProps } from '../../../primitive/cylinder'; -async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) { +export interface PolymerBackboneCylinderProps { + sizeTheme: SizeThemeProps + radialSegments: number +} + +async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerBackboneCylinderProps, mesh?: Mesh) { const polymerElementCount = getPolymerElementCount(unit) if (!polymerElementCount) return Mesh.createEmpty(mesh) - console.log('polymerElementCount backbone', polymerElementCount) - // TODO better vertex count estimates - const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh) + const sizeTheme = SizeTheme(props.sizeTheme) + const { radialSegments } = props + + const vertexCountEstimate = radialSegments * 2 * polymerElementCount * 2 + const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh) const { elements } = unit const pos = unit.conformation.invariantPosition const pA = Vec3.zero() const pB = Vec3.zero() + const l = StructureElement.create(unit) + const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments } let i = 0 const polymerBackboneIt = PolymerBackboneIterator(unit) while (polymerBackboneIt.hasNext) { - // TODO size theme const { centerA, centerB } = polymerBackboneIt.move() - pos(elements[centerA.element], pA) - pos(elements[centerB.element], pB) + const elmA = elements[centerA.element] + const elmB = elements[centerB.element] + pos(elmA, pA) + pos(elmB, pB) + + l.element = elmA + cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l) builder.setId(centerA.element) - builder.addCylinder(pA, pB, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) + builder.addCylinder(pA, pB, 0.5, cylinderProps) + + l.element = elmB + cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l) builder.setId(centerB.element) - builder.addCylinder(pB, pA, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) + builder.addCylinder(pB, pA, 0.5, cylinderProps) if (i % 10000 === 0 && ctx.shouldUpdate) { await ctx.update({ message: 'Backbone mesh', current: i, max: polymerElementCount }); @@ -50,7 +68,8 @@ async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit } export const DefaultPolymerBackboneProps = { - ...DefaultUnitsMeshProps + ...DefaultUnitsMeshProps, + radialSegments: 16 } export type PolymerBackboneProps = typeof DefaultPolymerBackboneProps diff --git a/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts b/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts index 9e33253f482b704f1124d724a027e3aa6641ca87..2416ef089851893f697d471c8f200e9df4bb5662 100644 --- a/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts +++ b/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts @@ -15,6 +15,7 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types'; import { StructureElementIterator } from './util/location-iterator'; import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; const t = Mat4.identity() const sVec = Vec3.zero() @@ -22,13 +23,22 @@ const n0 = Vec3.zero() const n1 = Vec3.zero() const upVec = Vec3.zero() -async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) { +const depthFactor = 4 +const widthFactor = 4 +const heightFactor = 6 + +export interface PolymerDirectionWedgeProps { + sizeTheme: SizeThemeProps +} + +async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, props: PolymerDirectionWedgeProps, mesh?: Mesh) { const polymerElementCount = getPolymerElementCount(unit) - console.log('polymerElementCount direction', polymerElementCount) if (!polymerElementCount) return Mesh.createEmpty(mesh) - // TODO better vertex count estimates - const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh) + const sizeTheme = SizeTheme(props.sizeTheme) + + const vertexCount = polymerElementCount * 24 + const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh) const linearSegments = 1 const state = createCurveSegmentState(linearSegments) @@ -47,18 +57,15 @@ async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, interpolateCurveSegment(state, v, tension) if ((isSheet && !v.secStrucChange) || !isSheet) { + const size = sizeTheme.size(v.center) + const depth = depthFactor * size + const width = widthFactor * size + const height = heightFactor * size - let width = 0.5, height = 1.2, depth = 0.6 - if (isNucleic) { - Vec3.fromArray(n0, binormalVectors, 0) - Vec3.fromArray(n1, binormalVectors, 3) - Vec3.normalize(upVec, Vec3.add(upVec, n0, n1)) - depth = 0.9 - } else { - Vec3.fromArray(n0, normalVectors, 0) - Vec3.fromArray(n1, normalVectors, 3) - Vec3.normalize(upVec, Vec3.add(upVec, n0, n1)) - } + const vectors = isNucleic ? binormalVectors : normalVectors + Vec3.fromArray(n0, vectors, 0) + Vec3.fromArray(n1, vectors, 3) + Vec3.normalize(upVec, Vec3.add(upVec, n0, n1)) Mat4.targetTo(t, v.p3, v.p1, upVec) Mat4.mul(t, t, Mat4.rotY90Z180) diff --git a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts index 1148392076c5e644efed0a8418f76c39fd928863..f5a12fdc6673a805a0e2a2c252ac2dc3b54476a1 100644 --- a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from 'mol-model/structure'; +import { Unit, StructureElement } from 'mol-model/structure'; import { UnitsVisual } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../shape/mesh'; @@ -14,36 +14,58 @@ import { getElementLoci, markElement } from './util/element'; import { Vec3 } from 'mol-math/linear-algebra'; import { StructureElementIterator } from './util/location-iterator'; import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; +import { CylinderProps } from '../../../primitive/cylinder'; -async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) { +const segmentCount = 10 + +export interface PolymerGapCylinderProps { + sizeTheme: SizeThemeProps + radialSegments: number +} + +async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerGapCylinderProps, mesh?: Mesh) { const polymerGapCount = getPolymerGapCount(unit) if (!polymerGapCount) return Mesh.createEmpty(mesh) - console.log('polymerGapCount', polymerGapCount) - // TODO better vertex count estimates - const builder = MeshBuilder.create(polymerGapCount * 30, polymerGapCount * 30 / 2, mesh) + const sizeTheme = SizeTheme(props.sizeTheme) + const { radialSegments } = props + + const vertexCountEstimate = segmentCount * radialSegments * 2 * polymerGapCount * 2 + const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh) const { elements } = unit const pos = unit.conformation.invariantPosition const pA = Vec3.zero() const pB = Vec3.zero() + const l = StructureElement.create(unit) + const cylinderProps: CylinderProps = { + radiusTop: 1, radiusBottom: 1, topCap: true, bottomCap: true, radialSegments + } let i = 0 const polymerGapIt = PolymerGapIterator(unit) while (polymerGapIt.hasNext) { - // TODO size theme const { centerA, centerB } = polymerGapIt.move() if (centerA.element === centerB.element) { builder.setId(centerA.element) pos(elements[centerA.element], pA) builder.addSphere(pA, 0.6, 0) } else { - pos(elements[centerA.element], pA) - pos(elements[centerB.element], pB) + const elmA = elements[centerA.element] + const elmB = elements[centerB.element] + pos(elmA, pA) + pos(elmB, pB) + + l.element = elmA + cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l) builder.setId(centerA.element) - builder.addFixedCountDashedCylinder(pA, pB, 0.5, 10, { radiusTop: 0.2, radiusBottom: 0.2 }) + builder.addFixedCountDashedCylinder(pA, pB, 0.5, segmentCount, cylinderProps) + + l.element = elmB + cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l) builder.setId(centerB.element) - builder.addFixedCountDashedCylinder(pB, pA, 0.5, 10, { radiusTop: 0.2, radiusBottom: 0.2 }) + builder.addFixedCountDashedCylinder(pB, pA, 0.5, segmentCount, cylinderProps) } if (i % 10000 === 0 && ctx.shouldUpdate) { @@ -56,7 +78,8 @@ async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, pro } export const DefaultPolymerGapProps = { - ...DefaultUnitsMeshProps + ...DefaultUnitsMeshProps, + radialSegments: 16 } export type PolymerGapProps = typeof DefaultPolymerGapProps diff --git a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts index 7373c39580e602e5f1356c13113f4505d2db138a..86ee412bf4c70f4ec68f00c75b8e429cea4cb553 100644 --- a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts +++ b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts @@ -5,7 +5,7 @@ */ import { Unit } from 'mol-model/structure'; -import { UnitsVisual } from '..'; +import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { markElement, getElementLoci } from './util/element'; import { Mesh } from '../../../shape/mesh'; @@ -14,18 +14,27 @@ import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState, import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types'; import { StructureElementIterator } from './util/location-iterator'; import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; +import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; + +export interface PolymerTraceMeshProps { + sizeTheme: SizeThemeProps + linearSegments: number + radialSegments: number + aspectRatio: number + arrowFactor: number +} // TODO handle polymer ends properly -async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) { +async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: PolymerTraceMeshProps, mesh?: Mesh) { const polymerElementCount = getPolymerElementCount(unit) - console.log('polymerElementCount trace', polymerElementCount) if (!polymerElementCount) return Mesh.createEmpty(mesh) - // TODO better vertex count estimates - const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh) - const linearSegments = 8 - const radialSegments = 12 + const sizeTheme = SizeTheme(props.sizeTheme) + const { linearSegments, radialSegments, aspectRatio, arrowFactor } = props + + const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2 + const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh) const state = createCurveSegmentState(linearSegments) const { curvePoints, normalVectors, binormalVectors } = state @@ -41,21 +50,23 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: {} const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix) const tension = (isNucleic || isSheet) ? 0.5 : 0.9 - // console.log('ELEMENT', i) interpolateCurveSegment(state, v, tension) - let width = 0.2, height = 0.2 + let width = sizeTheme.size(v.center) - // TODO size theme if (isSheet) { - width = 0.15; height = 1.0 - const arrowHeight = v.secStrucChange ? 1.7 : 0 + const height = width * aspectRatio + const arrowHeight = v.secStrucChange ? height * arrowFactor : 0 builder.addSheet(curvePoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, true, true) } else { + let height: number if (isHelix) { - width = 0.2; height = 1.0 + height = width * aspectRatio } else if (isNucleic) { - width = 1.5; height = 0.3 + height = width * aspectRatio; + [width, height] = [height, width] + } else { + height = width } builder.addTube(curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, true, true) } @@ -70,7 +81,11 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: {} } export const DefaultPolymerTraceProps = { - ...DefaultUnitsMeshProps + ...DefaultUnitsMeshProps, + linearSegments: 8, + radialSegments: 12, + aspectRatio: 5, + arrowFactor: 1.5 } export type PolymerTraceProps = typeof DefaultPolymerTraceProps @@ -81,6 +96,13 @@ export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> { createLocationIterator: StructureElementIterator.fromGroup, getLoci: getElementLoci, mark: markElement, - setUpdateState: () => {} + setUpdateState: (state: MeshUpdateState, newProps: PolymerTraceProps, currentProps: PolymerTraceProps) => { + state.createMesh = ( + newProps.linearSegments !== currentProps.linearSegments || + newProps.radialSegments !== currentProps.radialSegments || + newProps.aspectRatio !== currentProps.aspectRatio || + newProps.arrowFactor !== currentProps.arrowFactor + ) + } }) } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/util/link.ts b/src/mol-geo/representation/structure/visual/util/link.ts index 7959b43c0b1566ae5ba919823bc9c0d7f39d40e6..fa687917bbe58f663a6f17704de70c78d5a1341e 100644 --- a/src/mol-geo/representation/structure/visual/util/link.ts +++ b/src/mol-geo/representation/structure/visual/util/link.ts @@ -10,9 +10,12 @@ import { Mesh } from '../../../../shape/mesh'; import { MeshBuilder } from '../../../../shape/mesh-builder'; import { LinkType } from 'mol-model/structure/model/types'; import { DefaultMeshProps } from '../../../util'; +import { SizeThemeProps } from 'mol-view/theme/size'; +import { CylinderProps } from '../../../../primitive/cylinder'; export const DefaultLinkCylinderProps = { ...DefaultMeshProps, + sizeTheme: { name: 'uniform', value: 0.15 } as SizeThemeProps, linkScale: 0.4, linkSpacing: 1, linkRadius: 0.25, @@ -55,7 +58,8 @@ export interface LinkCylinderMeshBuilderProps { referencePosition(edgeIndex: number): Vec3 | null position(posA: Vec3, posB: Vec3, edgeIndex: number): void order(edgeIndex: number): number - flags(edgeIndex: number): LinkType.Flag + flags(edgeIndex: number): LinkType + radius(edgeIndex: number): number } /** @@ -63,39 +67,32 @@ export interface LinkCylinderMeshBuilderProps { * the half closer to the first vertex, i.e. vertex a. */ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: LinkCylinderMeshBuilderProps, props: LinkCylinderProps, mesh?: Mesh) { - const { linkCount, referencePosition, position, order, flags } = linkBuilder + const { linkCount, referencePosition, position, order, flags, radius } = linkBuilder if (!linkCount) return Mesh.createEmpty(mesh) - // approximate vertextCount (* 2), exact calculation would need to take - // multiple cylinders for bond orders and metall coordinations into account - const vertexCount = props.radialSegments * 2 * linkCount * 2 - const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh) + const { linkScale, linkSpacing, radialSegments } = props + + const vertexCountEstimate = radialSegments * 2 * linkCount * 2 + const meshBuilder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 4, mesh) const va = Vec3.zero() const vb = Vec3.zero() const vShift = Vec3.zero() - - const { linkScale, linkSpacing, linkRadius, radialSegments } = props - - const cylinderParams = { - height: 1, - radiusTop: linkRadius, - radiusBottom: linkRadius, - radialSegments - } + const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments } for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) { position(va, vb, edgeIndex) + const linkRadius = radius(edgeIndex) const o = order(edgeIndex) - const f = flags(edgeIndex) as any as LinkType // TODO + const f = flags(edgeIndex) meshBuilder.setId(edgeIndex) if (LinkType.is(f, LinkType.Flag.MetallicCoordination)) { // show metall coordinations with dashed cylinders - cylinderParams.radiusTop = cylinderParams.radiusBottom = linkRadius / 3 - meshBuilder.addFixedCountDashedCylinder(va, vb, 0.5, 7, cylinderParams) + cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius / 3 + meshBuilder.addFixedCountDashedCylinder(va, vb, 0.5, 7, cylinderProps) } else if (o === 2 || o === 3) { // show bonds with order 2 or 3 using 2 or 3 parallel cylinders const multiRadius = linkRadius * (linkScale / (0.5 * o)) @@ -104,13 +101,13 @@ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: L calculateShiftDir(vShift, va, vb, referencePosition(edgeIndex)) Vec3.setMagnitude(vShift, vShift, absOffset) - cylinderParams.radiusTop = cylinderParams.radiusBottom = multiRadius + cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius - if (o === 3) meshBuilder.addCylinder(va, vb, 0.5, cylinderParams) - meshBuilder.addDoubleCylinder(va, vb, 0.5, vShift, cylinderParams) + if (o === 3) meshBuilder.addCylinder(va, vb, 0.5, cylinderProps) + meshBuilder.addDoubleCylinder(va, vb, 0.5, vShift, cylinderProps) } else { - cylinderParams.radiusTop = cylinderParams.radiusBottom = linkRadius - meshBuilder.addCylinder(va, vb, 0.5, cylinderParams) + cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius + meshBuilder.addCylinder(va, vb, 0.5, cylinderProps) } if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) { diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index f490b055a8bd19eca33927007bf7db3b8ef18b3c..81eff8fd786debfa5592a46ed8e77b5c43ba808c 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -15,6 +15,7 @@ import { BallAndStickProps } from 'mol-geo/representation/structure/representati import { CartoonProps } from 'mol-geo/representation/structure/representation/cartoon'; import { DistanceRestraintProps } from 'mol-geo/representation/structure/representation/distance-restraint'; import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone'; +import { CarbohydrateProps } from 'mol-geo/representation/structure/representation/carbohydrate'; // import { Queries as Q, StructureProperties as SP, Query, Selection } from 'mol-model/structure'; const spacefillProps: Partial<SpacefillProps> = { @@ -44,7 +45,7 @@ const distanceRestraintProps: Partial<DistanceRestraintProps> = { const backboneProps: Partial<BackboneProps> = { doubleSided: true, colorTheme: { name: 'chain-id' }, - // colorTheme: { name: 'uniform', value: 0xFF0000 }, + sizeTheme: { name: 'uniform', value: 0.3 }, quality: 'auto', useFog: false, alpha: 0.5 @@ -53,16 +54,17 @@ const backboneProps: Partial<BackboneProps> = { const cartoonProps: Partial<CartoonProps> = { doubleSided: true, colorTheme: { name: 'chain-id' }, - // colorTheme: { name: 'uniform', value: 0x2200CC }, + sizeTheme: { name: 'uniform', value: 0.13, factor: 1 }, + aspectRatio: 8, quality: 'auto', useFog: false } -const carbohydrateProps: Partial<CartoonProps> = { +const carbohydrateProps: Partial<CarbohydrateProps> = { doubleSided: true, colorTheme: { name: 'carbohydrate-symbol' }, - // colorTheme: { name: 'uniform', value: 0x2200CC }, - quality: 'auto', + sizeTheme: { name: 'uniform', value: 1, factor: 1 }, + quality: 'highest', useFog: false } @@ -86,7 +88,7 @@ export class Stage { // this.loadPdbid('1hrv') // viral assembly // this.loadPdbid('1rb8') // virus // this.loadPdbid('1blu') // metal coordination - // this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein + this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein // this.loadPdbid('4v5a') // ribosome // this.loadPdbid('3j3q') // ... // this.loadPdbid('2np2') // dna @@ -114,7 +116,7 @@ export class Stage { // this.loadPdbid('4zs9') // contains raffinose // this.loadPdbid('2yft') // contains kestose // this.loadPdbid('2b5t') // contains large carbohydrate polymer - this.loadPdbid('1b5f') // contains carbohydrate with alternate locations + // this.loadPdbid('1b5f') // contains carbohydrate with alternate locations // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`) // this.loadMmcifUrl(`../../examples/1crn.cif`)