|
@@ -5,151 +5,73 @@
|
|
|
* @author David Sehnal <david.sehnal@gmail.com>
|
|
|
*/
|
|
|
|
|
|
-// TODO multiple cylinders for higher bond orders
|
|
|
-
|
|
|
import { ValueCell } from 'mol-util/value-cell'
|
|
|
|
|
|
import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
|
|
|
import { Unit, Link } from 'mol-model/structure';
|
|
|
import { UnitsVisual, DefaultStructureProps } from '../index';
|
|
|
import { RuntimeContext } from 'mol-task'
|
|
|
-import { createTransforms } from '../utils';
|
|
|
+import { DefaultLinkCylinderProps, LinkCylinderProps, createLinkCylinderMesh } from './util/link';
|
|
|
import { fillSerial } from 'mol-gl/renderable/util';
|
|
|
import { RenderableState, MeshValues } from 'mol-gl/renderable';
|
|
|
import { getMeshData } from '../../../util/mesh-data';
|
|
|
import { Mesh } from '../../../shape/mesh';
|
|
|
import { PickingId } from '../../../util/picking';
|
|
|
-import { MeshBuilder } from '../../../shape/mesh-builder';
|
|
|
-import { Vec3, Mat4 } from 'mol-math/linear-algebra';
|
|
|
+import { Vec3 } from 'mol-math/linear-algebra';
|
|
|
// import { createUniformColor } from '../../../util/color-data';
|
|
|
import { defaults } from 'mol-util';
|
|
|
import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
|
|
|
import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../../../util/marker-data';
|
|
|
import { SizeTheme } from '../../../theme';
|
|
|
import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id';
|
|
|
+import { createTransforms } from './util/common';
|
|
|
|
|
|
-const DefaultLinkCylinderProps = {
|
|
|
- linkScale: 0.4,
|
|
|
- linkSpacing: 1,
|
|
|
- linkRadius: 0.25,
|
|
|
- radialSegments: 16
|
|
|
-}
|
|
|
-type LinkCylinderProps = typeof DefaultLinkCylinderProps
|
|
|
-
|
|
|
-async function createLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) {
|
|
|
+async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) {
|
|
|
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
|
|
|
|
|
|
const elements = unit.elements;
|
|
|
const links = unit.links
|
|
|
const { edgeCount, a, b, edgeProps, offset } = links
|
|
|
- const orders = edgeProps.order
|
|
|
+ const { order } = edgeProps
|
|
|
|
|
|
if (!edgeCount) return Mesh.createEmpty(mesh)
|
|
|
|
|
|
- // approximate vertextCount, exact calculation would need to take link orders into account
|
|
|
- const vertexCount = 32 * edgeCount
|
|
|
- const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
|
|
|
-
|
|
|
- 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 vRef = Vec3.zero()
|
|
|
|
|
|
- const { linkScale, linkSpacing, linkRadius, radialSegments } = props
|
|
|
+ const pos = unit.conformation.invariantPosition
|
|
|
|
|
|
- const cylinderParams = {
|
|
|
- height: 1,
|
|
|
- radiusTop: linkRadius,
|
|
|
- radiusBottom: linkRadius,
|
|
|
- radialSegments,
|
|
|
- openEnded: true
|
|
|
+ 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 });
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- const pos = unit.conformation.invariantPosition
|
|
|
- // const l = Element.Location()
|
|
|
- // l.unit = unit
|
|
|
-
|
|
|
- // assumes aI < bI
|
|
|
- function getRefPos(aI: number, bI: number) {
|
|
|
- if (aI > bI) console.log('aI > bI')
|
|
|
+ function getRefPos(aI: number, bI: number): Vec3 | null {
|
|
|
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)
|
|
|
}
|
|
|
- // console.log('no ref', aI, bI, unit.model.atomicHierarchy.atoms.auth_atom_id.value(aI), unit.model.atomicHierarchy.atoms.auth_atom_id.value(bI), offset[aI], offset[aI + 1], offset[bI], offset[bI + 1], offset)
|
|
|
return null
|
|
|
}
|
|
|
|
|
|
- for (let edgeIndex = 0, _eI = edgeCount * 2; edgeIndex < _eI; ++edgeIndex) {
|
|
|
- const aI = elements[a[edgeIndex]], bI = elements[b[edgeIndex]];
|
|
|
- // Each edge is included twice to allow for coloring/picking
|
|
|
- // the half closer to the first vertex, i.e. vertex a.
|
|
|
- pos(aI, va)
|
|
|
- pos(bI, vb)
|
|
|
- const d = Vec3.distance(va, vb)
|
|
|
-
|
|
|
- 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 = orders[edgeIndex]
|
|
|
- meshBuilder.setId(edgeIndex)
|
|
|
- cylinderParams.height = d / 2
|
|
|
-
|
|
|
- if (order === 2 || order === 3) {
|
|
|
- const multiRadius = linkRadius * (linkScale / (0.5 * order))
|
|
|
- const absOffset = (linkRadius - multiRadius) * linkSpacing
|
|
|
-
|
|
|
- if (aI < bI) {
|
|
|
- calculateShiftDir(vShift, va, vb, getRefPos(a[edgeIndex], b[edgeIndex]))
|
|
|
- } else {
|
|
|
- calculateShiftDir(vShift, vb, va, getRefPos(b[edgeIndex], a[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)
|
|
|
- }
|
|
|
-
|
|
|
- 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)
|
|
|
- } else {
|
|
|
- cylinderParams.radiusTop = linkRadius
|
|
|
- cylinderParams.radiusBottom = linkRadius
|
|
|
-
|
|
|
- Mat4.setTranslation(m, vc)
|
|
|
- meshBuilder.addCylinder(m, cylinderParams)
|
|
|
- }
|
|
|
+ function setPositions(posA: Vec3, posB: Vec3, edgeIndex: number): void {
|
|
|
+ pos(elements[a[edgeIndex]], posA)
|
|
|
+ pos(elements[b[edgeIndex]], posB)
|
|
|
+ }
|
|
|
|
|
|
- if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) {
|
|
|
- await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: edgeCount });
|
|
|
- }
|
|
|
+ function getOrder(edgeIndex: number): number {
|
|
|
+ return order[edgeIndex]
|
|
|
}
|
|
|
|
|
|
- return meshBuilder.getMesh()
|
|
|
+ const builder = { linkCount: edgeCount, eachLink, getRefPos, setPositions, getOrder }
|
|
|
+
|
|
|
+ return createLinkCylinderMesh(ctx, builder, props, mesh)
|
|
|
}
|
|
|
|
|
|
export const DefaultIntraUnitLinkProps = {
|
|
@@ -180,7 +102,7 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
|
|
|
const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0
|
|
|
const instanceCount = group.units.length
|
|
|
|
|
|
- mesh = await createLinkCylinderMesh(ctx, unit, currentProps)
|
|
|
+ mesh = await createIntraUnitLinkCylinderMesh(ctx, unit, currentProps)
|
|
|
|
|
|
if (ctx.shouldUpdate) await ctx.update('Computing link transforms');
|
|
|
const transforms = createTransforms(group)
|
|
@@ -295,34 +217,4 @@ function markLink(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, v
|
|
|
if (changed) {
|
|
|
ValueCell.update(tMarker, tMarker.ref.value)
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-const tmpShiftV12 = Vec3.zero()
|
|
|
-const tmpShiftV13 = Vec3.zero()
|
|
|
-
|
|
|
-/** Calculate 'shift' direction that is perpendiculat to v1 - v2 and goes through v3 */
|
|
|
-function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | null) {
|
|
|
- Vec3.sub(tmpShiftV12, v1, v2)
|
|
|
-
|
|
|
- if (v3 !== null) {
|
|
|
- Vec3.sub(tmpShiftV13, v1, v3)
|
|
|
- } else {
|
|
|
- Vec3.copy(tmpShiftV13, v1) // no reference point, use v1
|
|
|
- }
|
|
|
- Vec3.normalize(tmpShiftV13, tmpShiftV13)
|
|
|
-
|
|
|
- // ensure v13 and v12 are not colinear
|
|
|
- let dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
|
|
|
- if (1 - Math.abs(dp) < 1e-5) {
|
|
|
- Vec3.set(tmpShiftV13, 1, 0, 0)
|
|
|
- dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
|
|
|
- if (1 - Math.abs(dp) < 1e-5) {
|
|
|
- Vec3.set(tmpShiftV13, 0, 1, 0)
|
|
|
- dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Vec3.setMagnitude(tmpShiftV12, tmpShiftV12, dp)
|
|
|
- Vec3.sub(tmpShiftV13, tmpShiftV13, tmpShiftV12)
|
|
|
- return Vec3.normalize(out, tmpShiftV13)
|
|
|
}
|