diff --git a/src/mol-geo/primitive/cylinder.ts b/src/mol-geo/primitive/cylinder.ts index f1782fb3bcd5d1b81742ef2673a996e6e27b7bb7..cb49550f6478b8db8b8667aac6b448f3f46352a3 100644 --- a/src/mol-geo/primitive/cylinder.ts +++ b/src/mol-geo/primitive/cylinder.ts @@ -14,14 +14,15 @@ export const DefaultCylinderProps = { height: 1, radialSegments: 8, heightSegments: 1, - openEnded: false, + topCap: false, + bottomCap: false, thetaStart: 0.0, thetaLength: Math.PI * 2 } export type CylinderProps = Partial<typeof DefaultCylinderProps> export default function Cylinder(props?: CylinderProps) { - const { radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength } = { ...DefaultCylinderProps, ...props }; + const { radiusTop, radiusBottom, height, radialSegments, heightSegments, topCap, bottomCap, thetaStart, thetaLength } = { ...DefaultCylinderProps, ...props }; // buffers const indices: number[] = []; @@ -36,10 +37,8 @@ export default function Cylinder(props?: CylinderProps) { // generate geometry generateTorso(); - if (openEnded === false) { - if (radiusTop > 0) generateCap(true); - if (radiusBottom > 0) generateCap(false); - } + if (topCap && radiusTop > 0) generateCap(true); + if (bottomCap && radiusBottom > 0) generateCap(false); return { vertices: new Float32Array(vertices), diff --git a/src/mol-geo/representation/structure/ball-and-stick.ts b/src/mol-geo/representation/structure/ball-and-stick.ts index 6038d6b5691feab7c2e9e29c27807860e137774f..c5a4755ef2fb157c3e9514af94b80418b13d4aef 100644 --- a/src/mol-geo/representation/structure/ball-and-stick.ts +++ b/src/mol-geo/representation/structure/ball-and-stick.ts @@ -29,6 +29,7 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti return { get renderObjects() { + // return linkRepr.renderObjects return [ ...elmementRepr.renderObjects, ...linkRepr.renderObjects ] }, create: (structure: Structure, props: BallAndStickProps = {} as BallAndStickProps) => { 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 569ae8c83edc9f4ebef72f6cb1eaafa30d1ca2de..b1852e23a216c80e5eac7ca424898f61ee63bc48 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,34 +31,23 @@ async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: S if (!bondCount) return Mesh.createEmpty(mesh) - async function eachLink(ctx: RuntimeContext, cb: (edgeIndex: number, aI: number, bI: number) => void) { - for (let edgeIndex = 0, il = bondCount; edgeIndex < il; ++edgeIndex) { - const b = bonds[edgeIndex] - const aI = b.indexA, bI = b.indexB; - cb(edgeIndex, aI, bI) - if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) { - await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: bondCount }); - } - } - } - - function getRefPos(aI: number, bI: number): Vec3 | null { + function referencePosition(edgeIndex: number): Vec3 | null { // TODO return null } - function setPositions(posA: Vec3, posB: Vec3, edgeIndex: number): void { + 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 getOrder(edgeIndex: number): number { + function order(edgeIndex: number): number { return bonds[edgeIndex].order } - const linkBuilder = { linkCount: bondCount, eachLink, getRefPos, setPositions, getOrder } + const linkBuilder = { linkCount: bondCount, referencePosition, position, order } return createLinkCylinderMesh(ctx, linkBuilder, 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 4449969d209350a83ca7ba85ac7464fdba3ad426..8d8b2f199b875a85dce6845ce3483654d075a632 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,7 +32,7 @@ async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, const elements = unit.elements; const links = unit.links const { edgeCount, a, b, edgeProps, offset } = links - const { order } = edgeProps + const { order: _order } = edgeProps if (!edgeCount) return Mesh.createEmpty(mesh) @@ -40,17 +40,9 @@ async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, const pos = unit.conformation.invariantPosition - async function eachLink(ctx: RuntimeContext, cb: (edgeIndex: number, aI: number, bI: number) => void) { - for (let edgeIndex = 0, _eI = edgeCount * 2; edgeIndex < _eI; ++edgeIndex) { - const aI = a[edgeIndex], bI = b[edgeIndex]; - cb(edgeIndex, aI, bI) - if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) { - await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: edgeCount }); - } - } - } - - function getRefPos(aI: number, bI: number): Vec3 | null { + 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) } @@ -60,16 +52,16 @@ async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, return null } - function setPositions(posA: Vec3, posB: Vec3, edgeIndex: number): void { + function position(posA: Vec3, posB: Vec3, edgeIndex: number): void { pos(elements[a[edgeIndex]], posA) pos(elements[b[edgeIndex]], posB) } - function getOrder(edgeIndex: number): number { - return order[edgeIndex] + function order(edgeIndex: number): number { + return _order[edgeIndex] } - const builder = { linkCount: edgeCount, eachLink, getRefPos, setPositions, getOrder } + const builder = { linkCount: edgeCount * 2, referencePosition, position, order } return createLinkCylinderMesh(ctx, builder, props, mesh) } diff --git a/src/mol-geo/representation/structure/visual/util/element.ts b/src/mol-geo/representation/structure/visual/util/element.ts index c34a3d3d2d3db58e74b2ec5cdf1200c734719aa2..396b24996ac023859f221a7678d016238d388658 100644 --- a/src/mol-geo/representation/structure/visual/util/element.ts +++ b/src/mol-geo/representation/structure/visual/util/element.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Vec3, Mat4 } from 'mol-math/linear-algebra'; +import { Vec3 } from 'mol-math/linear-algebra'; import { Unit, Element } from 'mol-model/structure'; import { SizeTheme } from '../../../../theme'; import { RuntimeContext } from 'mol-task'; @@ -36,8 +36,6 @@ export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, r const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh) const v = Vec3.zero() - const m = Mat4.identity() - const pos = unit.conformation.invariantPosition const l = Element.Location() l.unit = unit @@ -45,10 +43,9 @@ export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, r for (let i = 0; i < elementCount; i++) { l.element = elements[i] pos(elements[i], v) - Mat4.setTranslation(m, v) meshBuilder.setId(i) - meshBuilder.addIcosahedron(m, { radius: radius(l), detail }) + meshBuilder.addIcosahedron(v, radius(l), detail) if (i % 10000 === 0 && ctx.shouldUpdate) { await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount }); diff --git a/src/mol-geo/representation/structure/visual/util/link.ts b/src/mol-geo/representation/structure/visual/util/link.ts index a38d9983724df8153d9cf7b12795a49d2a8556f3..52079675792df5de3f4f06a971e963d7e08e2523 100644 --- a/src/mol-geo/representation/structure/visual/util/link.ts +++ b/src/mol-geo/representation/structure/visual/util/link.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Vec3, Mat4 } from 'mol-math/linear-algebra'; +import { Vec3 } from 'mol-math/linear-algebra'; import { RuntimeContext } from 'mol-task'; import { Mesh } from '../../../../shape/mesh'; import { MeshBuilder } from '../../../../shape/mesh-builder'; @@ -22,7 +22,7 @@ const tmpShiftV13 = Vec3.zero() /** Calculate 'shift' direction that is perpendiculat to v1 - v2 and goes through v3 */ export function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | null) { - Vec3.sub(tmpShiftV12, v1, v2) + Vec3.normalize(tmpShiftV12, Vec3.sub(tmpShiftV12, v1, v2)) if (v3 !== null) { Vec3.sub(tmpShiftV13, v1, v3) @@ -47,21 +47,19 @@ export function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | nul return Vec3.normalize(out, tmpShiftV13) } -export interface LinkCylinderMeshBuilder { +export interface LinkCylinderMeshBuilderProps { linkCount: number - eachLink(ctx: RuntimeContext, cb: (edgeIndex: number, aI: number, bI: number) => void): Promise<void> - // assumes aI < bI - getRefPos(aI: number, bI: number): Vec3 | null - setPositions(posA: Vec3, posB: Vec3, edgeIndex: number): void - getOrder(edgeIndex: number): number + referencePosition(edgeIndex: number): Vec3 | null + position(posA: Vec3, posB: Vec3, edgeIndex: number): void + order(edgeIndex: number): number } /** * Each edge is included twice to allow for coloring/picking * the half closer to the first vertex, i.e. vertex a. */ -export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: LinkCylinderMeshBuilder, props: LinkCylinderProps, mesh?: Mesh) { - const { linkCount, eachLink, getRefPos, setPositions, getOrder } = linkBuilder +export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: LinkCylinderMeshBuilderProps, props: LinkCylinderProps, mesh?: Mesh) { + const { linkCount, referencePosition, position, order } = linkBuilder if (!linkCount) return Mesh.createEmpty(mesh) @@ -71,13 +69,7 @@ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: L const va = Vec3.zero() const vb = Vec3.zero() - const vd = Vec3.zero() - const vc = Vec3.zero() - const m = Mat4.identity() - const mt = Mat4.identity() - const vShift = Vec3.zero() - const vCenter = Vec3.zero() const { linkScale, linkSpacing, linkRadius, radialSegments } = props @@ -85,65 +77,35 @@ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: L height: 1, radiusTop: linkRadius, radiusBottom: linkRadius, - radialSegments, - openEnded: true + radialSegments } - // for (let edgeIndex = 0, _eI = edgeCount * 2; edgeIndex < _eI; ++edgeIndex) { - await eachLink(ctx, (edgeIndex, aI, bI) => { - // const aI = a[edgeIndex], bI = b[edgeIndex]; - - setPositions(va, vb, edgeIndex) - const d = Vec3.distance(va, vb) + for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) { + position(va, vb, edgeIndex) - Vec3.sub(vd, vb, va) - Vec3.scale(vd, Vec3.normalize(vd, vd), d / 4) - Vec3.add(vc, va, vd) - // ensure both edge halfs are pointing in the the same direction so the triangles align - if (aI > bI) Vec3.scale(vd, vd, -1) - Vec3.makeRotation(m, Vec3.create(0, 1, 0), vd) - - const order = getOrder(edgeIndex) + const o = order(edgeIndex) meshBuilder.setId(edgeIndex) - cylinderParams.height = d / 2 - if (order === 2 || order === 3) { - const multiRadius = linkRadius * (linkScale / (0.5 * order)) + if (o === 2 || o === 3) { + const multiRadius = linkRadius * (linkScale / (0.5 * o)) const absOffset = (linkRadius - multiRadius) * linkSpacing - if (aI < bI) { - calculateShiftDir(vShift, va, vb, getRefPos(aI, bI)) - } else { - calculateShiftDir(vShift, vb, va, getRefPos(bI, aI)) - } + calculateShiftDir(vShift, va, vb, referencePosition(edgeIndex)) Vec3.setMagnitude(vShift, vShift, absOffset) - cylinderParams.radiusTop = multiRadius - cylinderParams.radiusBottom = multiRadius - - if (order === 3) { - Mat4.fromTranslation(mt, vc) - Mat4.mul(mt, mt, m) - meshBuilder.addCylinder(mt, cylinderParams) - } + cylinderParams.radiusTop = cylinderParams.radiusBottom = multiRadius - Vec3.add(vCenter, vc, vShift) - Mat4.fromTranslation(mt, vCenter) - Mat4.mul(mt, mt, m) - meshBuilder.addCylinder(mt, cylinderParams) - - Vec3.sub(vCenter, vc, vShift) - Mat4.fromTranslation(mt, vCenter) - Mat4.mul(mt, mt, m) - meshBuilder.addCylinder(mt, cylinderParams) + if (o === 3) meshBuilder.addCylinder(va, vb, 0.5, cylinderParams) + meshBuilder.addDoubleCylinder(va, vb, 0.5, vShift, cylinderParams) } else { - cylinderParams.radiusTop = linkRadius - cylinderParams.radiusBottom = linkRadius + cylinderParams.radiusTop = cylinderParams.radiusBottom = linkRadius + meshBuilder.addCylinder(va, vb, 0.5, cylinderParams) + } - Mat4.setTranslation(m, vc) - meshBuilder.addCylinder(m, cylinderParams) + if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) { + await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: linkCount }); } - }) + } return meshBuilder.getMesh() } \ No newline at end of file diff --git a/src/mol-geo/shape/mesh-builder.ts b/src/mol-geo/shape/mesh-builder.ts index 5efda4e2a1514e8f8eeb12b87a11eb871cb5dc06..cb72406c13ab18e2a5607c40ac055d72a97c5cfa 100644 --- a/src/mol-geo/shape/mesh-builder.ts +++ b/src/mol-geo/shape/mesh-builder.ts @@ -23,15 +23,68 @@ type Primitive = { export interface MeshBuilder { add(t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices?: Uint32Array): number addBox(t: Mat4, props?: BoxProps): number - addCylinder(t: Mat4, props?: CylinderProps): number - addIcosahedron(t: Mat4, props?: IcosahedronProps): 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 setId(id: number): void getMesh(): Mesh } +const cylinderMap = new Map<string, Primitive>() +const icosahedronMap = new Map<string, Primitive>() + +const up = Vec3.create(0, 1, 0) const tmpV = Vec3.zero() const tmpMat3 = Mat3.zero() +const tmpCylinderDir = Vec3.zero() +const tmpCylinderMatDir = Vec3.zero() +const tmpCylinderCenter = Vec3.zero() +const tmpCylinderMat = Mat4.zero() +// const tmpCylinderMatTrans = Mat4.zero() +const tmpShiftedCylinderStart = Vec3.zero() + +function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number) { + Vec3.setMagnitude(tmpCylinderMatDir, dir, length / 2) + Vec3.add(tmpCylinderCenter, start, tmpCylinderMatDir) + // ensure the direction use to create the rotation is always pointing in the same + // direction so the triangles of adjacent cylinder will line up + if (Vec3.dot(tmpCylinderMatDir, up) < 0) Vec3.scale(tmpCylinderMatDir, tmpCylinderMatDir, -1) + Vec3.makeRotation(m, up, tmpCylinderMatDir) + // Mat4.fromTranslation(tmpCylinderMatTrans, tmpCylinderCenter) + // Mat4.mul(m, tmpCylinderMatTrans, m) + Mat4.setTranslation(m, tmpCylinderCenter) + return m +} + +function getCylinder(props: CylinderProps) { + const key = JSON.stringify(props) + let cylinder = cylinderMap.get(key) + if (cylinder === undefined) { + cylinder = Cylinder(props) + cylinderMap.set(key, cylinder) + } + return cylinder +} + +const tmpIcosahedronMat = Mat4.identity() + +function setIcosahedronMat(m: Mat4, center: Vec3) { + Mat4.setTranslation(m, center) + return m +} + +function getIcosahedron(props: IcosahedronProps) { + const key = JSON.stringify(props) + let icosahedron = icosahedronMap.get(key) + if (icosahedron === undefined) { + icosahedron = Icosahedron(props) + icosahedronMap.set(key, icosahedron) + } + return icosahedron +} + // TODO cache primitives based on props export namespace MeshBuilder { @@ -45,10 +98,7 @@ export namespace MeshBuilder { let currentId = -1 - const cylinderMap = new Map<string, Primitive>() - const icosahedronMap = new Map<string, Primitive>() - - const add = (t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices: Uint32Array) => { + function add(t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices: Uint32Array) { const { elementCount, elementSize } = vertices const n = getNormalMatrix(tmpMat3, t) for (let i = 0, il = _vertices.length; i < il; i += 3) { @@ -60,7 +110,7 @@ export namespace MeshBuilder { Vec3.fromArray(tmpV, _normals, i) Vec3.transformMat3(tmpV, tmpV, n) ChunkedArray.add3(normals, tmpV[0], tmpV[1], tmpV[2]); - + // id ChunkedArray.add(ids, currentId); } for (let i = 0, il = _indices.length; i < il; i += 3) { @@ -75,23 +125,32 @@ export namespace MeshBuilder { const box = Box(props) return add(t, box.vertices, box.normals, box.indices) }, - addCylinder: (t: Mat4, props?: CylinderProps) => { - const key = JSON.stringify(props) - let cylinder = cylinderMap.get(key) - if (cylinder === undefined) { - cylinder = Cylinder(props) - cylinderMap.set(key, cylinder) - } - return add(t, cylinder.vertices, cylinder.normals, cylinder.indices) + addCylinder: (start: Vec3, end: Vec3, scale: number, props: CylinderProps) => { + const d = Vec3.distance(start, end) * scale + 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) }, - addIcosahedron: (t: Mat4, props: IcosahedronProps) => { - const key = JSON.stringify(props) - let icosahedron = icosahedronMap.get(key) - if (icosahedron === undefined) { - icosahedron = Icosahedron(props) - icosahedronMap.set(key, icosahedron) - } - return add(t, icosahedron.vertices, icosahedron.normals, icosahedron.indices) + addDoubleCylinder: (start: Vec3, end: Vec3, scale: number, shift: Vec3, props: CylinderProps) => { + const d = Vec3.distance(start, end) * scale + 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) + add(tmpCylinderMat, vertices, normals, indices) + // negativly shifted cylinder + Vec3.sub(tmpShiftedCylinderStart, start, shift) + setCylinderMat(tmpCylinderMat, tmpShiftedCylinderStart, tmpCylinderDir, d) + return 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) }, setId: (id: number) => { if (currentId !== id) {