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 b1852e23a216c80e5eac7ca424898f61ee63bc48..fa76a9e30682fb81d21be816edc22eb1c5f830ea 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 @@ -31,25 +31,20 @@ async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: S if (!bondCount) return Mesh.createEmpty(mesh) - function referencePosition(edgeIndex: number): Vec3 | null { - // TODO - return null - } - - function position(posA: Vec3, posB: Vec3, edgeIndex: number): void { - const b = bonds[edgeIndex] - const uA = b.unitA, uB = b.unitB - uA.conformation.position(uA.elements[b.indexA], posA) - uB.conformation.position(uB.elements[b.indexB], posB) - } - - function order(edgeIndex: number): number { - return bonds[edgeIndex].order + const builderProps = { + linkCount: bondCount, + referencePosition: (edgeIndex: number) => null, // TODO + position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { + const b = bonds[edgeIndex] + const uA = b.unitA, uB = b.unitB + uA.conformation.position(uA.elements[b.indexA], posA) + uB.conformation.position(uB.elements[b.indexB], posB) + }, + order: (edgeIndex: number) => bonds[edgeIndex].order, + flags: (edgeIndex: number) => bonds[edgeIndex].flag } - const linkBuilder = { linkCount: bondCount, referencePosition, position, order } - - return createLinkCylinderMesh(ctx, linkBuilder, props, mesh) + return createLinkCylinderMesh(ctx, builderProps, props, mesh) } export const DefaultInterUnitLinkProps = { 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 8d8b2f199b875a85dce6845ce3483654d075a632..15564c2cf637ee2879c72bb557fa48e7bd8fcab7 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 @@ -32,38 +32,35 @@ async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, const elements = unit.elements; const links = unit.links const { edgeCount, a, b, edgeProps, offset } = links - const { order: _order } = edgeProps + const { order: _order, flags: _flags } = edgeProps if (!edgeCount) return Mesh.createEmpty(mesh) const vRef = Vec3.zero() - const pos = unit.conformation.invariantPosition - function referencePosition(edgeIndex: number): Vec3 | null { - let aI = a[edgeIndex], bI = b[edgeIndex]; - if (aI > bI) [aI, bI] = [bI, aI] - for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) { - if (b[i] !== bI) return pos(elements[b[i]], vRef) - } - for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) { - if (a[i] !== aI) return pos(elements[a[i]], vRef) - } - return null - } - - function position(posA: Vec3, posB: Vec3, edgeIndex: number): void { - pos(elements[a[edgeIndex]], posA) - pos(elements[b[edgeIndex]], posB) - } - - function order(edgeIndex: number): number { - return _order[edgeIndex] + const builderProps = { + linkCount: edgeCount * 2, + referencePosition: (edgeIndex: number) => { + let aI = a[edgeIndex], bI = b[edgeIndex]; + if (aI > bI) [aI, bI] = [bI, aI] + for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) { + if (b[i] !== bI) return pos(elements[b[i]], vRef) + } + for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) { + if (a[i] !== aI) return pos(elements[a[i]], vRef) + } + return null + }, + position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { + pos(elements[a[edgeIndex]], posA) + pos(elements[b[edgeIndex]], posB) + }, + order: (edgeIndex: number) => _order[edgeIndex], + flags: (edgeIndex: number) => _flags[edgeIndex] } - const builder = { linkCount: edgeCount * 2, referencePosition, position, order } - - return createLinkCylinderMesh(ctx, builder, props, mesh) + return createLinkCylinderMesh(ctx, builderProps, props, mesh) } export const DefaultIntraUnitLinkProps = { diff --git a/src/mol-geo/representation/structure/visual/util/link.ts b/src/mol-geo/representation/structure/visual/util/link.ts index 52079675792df5de3f4f06a971e963d7e08e2523..4d23190b207f3cea2ca31bd5df6256758d01f6c3 100644 --- a/src/mol-geo/representation/structure/visual/util/link.ts +++ b/src/mol-geo/representation/structure/visual/util/link.ts @@ -8,6 +8,7 @@ import { Vec3 } from 'mol-math/linear-algebra'; import { RuntimeContext } from 'mol-task'; import { Mesh } from '../../../../shape/mesh'; import { MeshBuilder } from '../../../../shape/mesh-builder'; +import { LinkType } from 'mol-model/structure/model/types'; export const DefaultLinkCylinderProps = { linkScale: 0.4, @@ -52,6 +53,7 @@ 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 } /** @@ -59,12 +61,13 @@ 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 } = linkBuilder + const { linkCount, referencePosition, position, order, flags } = linkBuilder if (!linkCount) return Mesh.createEmpty(mesh) - // approximate vertextCount, exact calculation would need to take link orders into account - const vertexCount = 32 * linkCount + // 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 va = Vec3.zero() @@ -84,9 +87,15 @@ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: L position(va, vb, edgeIndex) const o = order(edgeIndex) + const f = flags(edgeIndex) as any as LinkType // TODO meshBuilder.setId(edgeIndex) - if (o === 2 || o === 3) { + 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) + } 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)) const absOffset = (linkRadius - multiRadius) * linkSpacing diff --git a/src/mol-geo/shape/mesh-builder.ts b/src/mol-geo/shape/mesh-builder.ts index cb72406c13ab18e2a5607c40ac055d72a97c5cfa..6eed0f441cc4772eb606763c1f731e67880c964c 100644 --- a/src/mol-geo/shape/mesh-builder.ts +++ b/src/mol-geo/shape/mesh-builder.ts @@ -21,12 +21,12 @@ type Primitive = { } export interface MeshBuilder { - add(t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices?: Uint32Array): number - addBox(t: Mat4, props?: BoxProps): number - addCylinder(start: Vec3, end: Vec3, scale: number, props: CylinderProps): number - addDoubleCylinder(start: Vec3, end: Vec3, scale: number, shift: Vec3, props: CylinderProps): number - // addFixedCountDashedCylinder(t: Mat4, props?: CylinderProps): number - addIcosahedron(center: Vec3, radius: number, detail: number): number + add(t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices?: Uint32Array): void + addBox(t: Mat4, props?: BoxProps): void + addCylinder(start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps): void + addDoubleCylinder(start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps): void + addFixedCountDashedCylinder(start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps): void + addIcosahedron(center: Vec3, radius: number, detail: number): void setId(id: number): void getMesh(): Mesh } @@ -43,7 +43,7 @@ const tmpCylinderMatDir = Vec3.zero() const tmpCylinderCenter = Vec3.zero() const tmpCylinderMat = Mat4.zero() // const tmpCylinderMatTrans = Mat4.zero() -const tmpShiftedCylinderStart = Vec3.zero() +const tmpCylinderStart = Vec3.zero() function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number) { Vec3.setMagnitude(tmpCylinderMatDir, dir, length / 2) @@ -99,7 +99,7 @@ export namespace MeshBuilder { let currentId = -1 function add(t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices: Uint32Array) { - const { elementCount, elementSize } = vertices + const { elementCount } = vertices const n = getNormalMatrix(tmpMat3, t) for (let i = 0, il = _vertices.length; i < il; i += 3) { // position @@ -116,41 +116,64 @@ export namespace MeshBuilder { for (let i = 0, il = _indices.length; i < il; i += 3) { ChunkedArray.add3(indices, _indices[i] + elementCount, _indices[i + 1] + elementCount, _indices[i + 2] + elementCount); } - return elementCount * elementSize } return { add, addBox: (t: Mat4, props?: BoxProps) => { const box = Box(props) - return add(t, box.vertices, box.normals, box.indices) + add(t, box.vertices, box.normals, box.indices) }, - addCylinder: (start: Vec3, end: Vec3, scale: number, props: CylinderProps) => { - const d = Vec3.distance(start, end) * scale + addCylinder: (start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps) => { + const d = Vec3.distance(start, end) * lengthScale props.height = d const { vertices, normals, indices } = getCylinder(props) Vec3.sub(tmpCylinderDir, end, start) setCylinderMat(tmpCylinderMat, start, tmpCylinderDir, d) - return add(tmpCylinderMat, vertices, normals, indices) + add(tmpCylinderMat, vertices, normals, indices) }, - addDoubleCylinder: (start: Vec3, end: Vec3, scale: number, shift: Vec3, props: CylinderProps) => { - const d = Vec3.distance(start, end) * scale + addDoubleCylinder: (start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps) => { + const d = Vec3.distance(start, end) * lengthScale props.height = d const { vertices, normals, indices } = getCylinder(props) Vec3.sub(tmpCylinderDir, end, start) // positivly shifted cylinder - Vec3.add(tmpShiftedCylinderStart, start, shift) - setCylinderMat(tmpCylinderMat, tmpShiftedCylinderStart, tmpCylinderDir, d) + Vec3.add(tmpCylinderStart, start, shift) + setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d) add(tmpCylinderMat, vertices, normals, indices) // negativly shifted cylinder - Vec3.sub(tmpShiftedCylinderStart, start, shift) - setCylinderMat(tmpCylinderMat, tmpShiftedCylinderStart, tmpCylinderDir, d) - return add(tmpCylinderMat, vertices, normals, indices) + Vec3.sub(tmpCylinderStart, start, shift) + setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d) + add(tmpCylinderMat, vertices, normals, indices) + }, + addFixedCountDashedCylinder: (start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps) => { + const s = Math.floor(segmentCount / 2) + const step = 1 / segmentCount + + // automatically adjust length so links/bonds that are rendered as two half cylinders + // have evenly spaced dashed cylinders + if (lengthScale < 1) { + const bias = lengthScale / 2 / segmentCount + lengthScale += segmentCount % 2 === 1 ? bias : -bias + } + + const d = Vec3.distance(start, end) * lengthScale + props.height = d * step + const { vertices, normals, indices } = getCylinder(props) + Vec3.sub(tmpCylinderDir, end, start) + + for (let j = 0; j < s; ++j) { + const f = step * (j * 2 + 1) + Vec3.setMagnitude(tmpCylinderDir, tmpCylinderDir, d * f) + Vec3.add(tmpCylinderStart, start, tmpCylinderDir) + setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d * step) + add(tmpCylinderMat, vertices, normals, indices) + } }, addIcosahedron: (center: Vec3, radius: number, detail: number) => { const { vertices, normals, indices } = getIcosahedron({ radius, detail }) setIcosahedronMat(tmpIcosahedronMat, center) - return add(tmpIcosahedronMat, vertices, normals, indices) + add(tmpIcosahedronMat, vertices, normals, indices) }, setId: (id: number) => { if (currentId !== id) { diff --git a/src/mol-util/bit-flags.ts b/src/mol-util/bit-flags.ts index 8143eb160bcbc0a1268cb3ddb9ef56d094d2d6e8..7f0bb8870771381b93be9e199f14e8bd52a4394e 100644 --- a/src/mol-util/bit-flags.ts +++ b/src/mol-util/bit-flags.ts @@ -10,7 +10,7 @@ namespace BitFlags { export function create<F>(flags: F): BitFlags<F> { return flags as any; } export function has<F>(flags: BitFlags<F>, flag: F) { return ((flags as any) & (flag as any)) !== 0; } - // toCheck must be non-zero + /** toCheck must be non-zero */ export function hasAll<F>(flags: BitFlags<F>, toCheck: BitFlags<F>) { return !!toCheck && ((flags as any) & (toCheck as any)) === (toCheck as any); } } diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 25d2ae1914a12d0de4d450ac7a5029380ff3c546..b9afd01aaf4d647893092389258bbd3b35f3dc75 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -42,9 +42,10 @@ export class Stage { // this.loadPdbid('1jj2') // this.loadPdbid('4umt') // ligand has bond with order 3 - // this.loadPdbid('1crn') - this.loadPdbid('3pqr') - // this.loadPdbid('4v5a') + // this.loadPdbid('1crn') // small + this.loadPdbid('1blu') // metal coordination + // this.loadPdbid('3pqr') // inter unit bonds + // this.loadPdbid('4v5a') // ribosome // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) }