Browse Source

cleanup, cylinder links & mesh builder

Alexander Rose 6 years ago
parent
commit
3f6a6b4ccc

+ 5 - 6
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),

+ 1 - 0
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) => {

+ 4 - 15
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)
 }

+ 8 - 16
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)
 }

+ 2 - 5
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 });

+ 24 - 62
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()
 }

+ 82 - 23
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) {