Browse Source

added sheet and tube caps

Alexander Rose 6 years ago
parent
commit
dcc1bbe74a

+ 121 - 28
src/mol-geo/primitive/sheet.ts

@@ -7,6 +7,7 @@
 
 import { Vec3 } from 'mol-math/linear-algebra';
 import { ChunkedArray } from 'mol-data/util';
+import { MeshBuilderState } from '../shape/mesh-builder';
 
 const tA = Vec3.zero()
 const tB = Vec3.zero()
@@ -19,29 +20,39 @@ const positionVector = Vec3.zero()
 const normalVector = Vec3.zero()
 const torsionVector = Vec3.zero()
 
-export function addSheet(controlPoints: Helpers.NumberArray, normalVectors: Helpers.NumberArray, binormalVectors: Helpers.NumberArray, linearSegments: number, width: number, height: number, arrowWidth: number, vertices: ChunkedArray<number, 3>, normals: ChunkedArray<number, 3>, indices: ChunkedArray<number, 3>, ids: ChunkedArray<number, 1>, currentId: number) {
+const arrowVerticalVector = Vec3.zero()
+const p1 = Vec3.zero()
+const p2 = Vec3.zero()
+const p3 = Vec3.zero()
+const p4 = Vec3.zero()
+const p5 = Vec3.zero()
+const p6 = Vec3.zero()
+const p7 = Vec3.zero()
+const p8 = Vec3.zero()
 
-    const vertexCount = vertices.elementCount
+export function addSheet(controlPoints: Helpers.NumberArray, normalVectors: Helpers.NumberArray, binormalVectors: Helpers.NumberArray, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean, state: MeshBuilderState) {
+    const { vertices, normals, indices } = state
+
+    let vertexCount = vertices.elementCount
     let offsetLength = 0
 
-    if (arrowWidth > 0) {
+    if (arrowHeight > 0) {
         Vec3.fromArray(tA, controlPoints, 0)
         Vec3.fromArray(tB, controlPoints, linearSegments * 3)
-        offsetLength = arrowWidth / Vec3.magnitude(Vec3.sub(tV, tB, tA))
+        offsetLength = arrowHeight / Vec3.magnitude(Vec3.sub(tV, tB, tA))
     }
 
     for (let i = 0; i <= linearSegments; ++i) {
-        const actualWidth = arrowWidth === 0 ? width : arrowWidth * (1 - i / linearSegments);
-
+        const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
         const i3 = i * 3
 
         Vec3.fromArray(verticalVector, normalVectors, i3)
-        Vec3.scale(verticalVector, verticalVector, actualWidth);
+        Vec3.scale(verticalVector, verticalVector, actualHeight);
 
         Vec3.fromArray(horizontalVector, binormalVectors, i3)
-        Vec3.scale(horizontalVector, horizontalVector, height);
+        Vec3.scale(horizontalVector, horizontalVector, width);
 
-        if (arrowWidth > 0) {
+        if (arrowHeight > 0) {
             Vec3.fromArray(tA, normalVectors, i3)
             Vec3.fromArray(tB, binormalVectors, i3)
             Vec3.scale(normalOffset, Vec3.cross(normalOffset, tA, tB), offsetLength)
@@ -51,49 +62,41 @@ export function addSheet(controlPoints: Helpers.NumberArray, normalVectors: Help
         Vec3.fromArray(normalVector, normalVectors, i3)
         Vec3.fromArray(torsionVector, binormalVectors, i3)
 
-        Vec3.add(tA, Vec3.add(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
+        Vec3.add(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector)
         Vec3.copy(tB, normalVector)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
 
-        Vec3.add(tA, Vec3.sub(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
+        Vec3.add(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
 
-        Vec3.add(tA, Vec3.sub(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
-        Vec3.add(tB, Vec3.scale(tB, Vec3.copy(tB, torsionVector), -1), normalOffset)
+        // Vec3.add(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector) // reuse tA
+        Vec3.add(tB, Vec3.negate(tB, torsionVector), normalOffset)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
 
-        Vec3.sub(tA, Vec3.sub(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
+        Vec3.sub(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
 
-        Vec3.sub(tA, Vec3.sub(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
-        Vec3.scale(tB, Vec3.copy(tB, normalVector), -1)
+        // Vec3.sub(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector) // reuse tA
+        Vec3.negate(tB, normalVector)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
 
-        Vec3.sub(tA, Vec3.add(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
+        Vec3.sub(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
 
-        Vec3.sub(tA, Vec3.add(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
-        Vec3.add(tB, Vec3.copy(tB, torsionVector), normalOffset)
+        // Vec3.sub(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector) // reuse tA
+        Vec3.add(tB, torsionVector, normalOffset)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
 
-        Vec3.add(tA, Vec3.add(tA, Vec3.copy(tA, positionVector), horizontalVector), verticalVector)
+        Vec3.add(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector)
         ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
         ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
-        ChunkedArray.add(ids, currentId);
     }
 
     for (let i = 0; i < linearSegments; ++i) {
@@ -112,4 +115,94 @@ export function addSheet(controlPoints: Helpers.NumberArray, normalVectors: Help
             );
         }
     }
+
+    if (startCap) {
+        const offset = 0
+        vertexCount = vertices.elementCount
+
+        Vec3.fromArray(verticalVector, normalVectors, offset)
+        Vec3.scale(verticalVector, verticalVector, height);
+
+        Vec3.fromArray(horizontalVector, binormalVectors, offset)
+        Vec3.scale(horizontalVector, horizontalVector, width);
+
+        Vec3.fromArray(positionVector, controlPoints, offset)
+
+        Vec3.add(p1, Vec3.add(p1, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p2, Vec3.add(p2, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p3, Vec3.sub(p3, positionVector, horizontalVector), verticalVector)
+        Vec3.add(p4, Vec3.sub(p4, positionVector, horizontalVector), verticalVector)
+
+        ChunkedArray.add3(vertices, p1[0], p1[1], p1[2])
+        ChunkedArray.add3(vertices, p2[0], p2[1], p2[2])
+        ChunkedArray.add3(vertices, p3[0], p3[1], p3[2])
+        ChunkedArray.add3(vertices, p4[0], p4[1], p4[2])
+
+        Vec3.cross(normalVector, horizontalVector, verticalVector)
+
+        if (arrowHeight === 0) {
+            for (let i = 0; i < 4; ++i) {
+                ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2])
+            }
+
+            ChunkedArray.add3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
+            ChunkedArray.add3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
+        } else {
+            Vec3.fromArray(arrowVerticalVector, normalVectors, offset)
+            Vec3.scale(arrowVerticalVector, verticalVector, arrowHeight);
+
+            Vec3.add(p5, Vec3.add(p5, positionVector, horizontalVector), arrowVerticalVector)
+            Vec3.sub(p6, Vec3.add(p6, positionVector, horizontalVector), arrowVerticalVector)
+            Vec3.sub(p7, Vec3.sub(p7, positionVector, horizontalVector), arrowVerticalVector)
+            Vec3.add(p8, Vec3.sub(p8, positionVector, horizontalVector), arrowVerticalVector)
+
+            ChunkedArray.add3(vertices, p5[0], p5[1], p5[2])
+            ChunkedArray.add3(vertices, p6[0], p6[1], p6[2])
+            ChunkedArray.add3(vertices, p7[0], p7[1], p7[2])
+            ChunkedArray.add3(vertices, p8[0], p8[1], p8[2])
+
+            for (let i = 0; i < 8; ++i) {
+                ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2])
+            }
+
+            ChunkedArray.add3(indices, vertexCount + 7, vertexCount, vertexCount + 4);
+            ChunkedArray.add3(indices, vertexCount + 7, vertexCount + 3, vertexCount);
+            ChunkedArray.add3(indices, vertexCount + 5, vertexCount + 1, vertexCount + 6);
+            ChunkedArray.add3(indices, vertexCount + 1, vertexCount + 2, vertexCount + 6);
+        }
+    }
+
+    if (endCap && arrowHeight === 0) {
+        const offset = linearSegments * 3
+        vertexCount = vertices.elementCount
+
+        Vec3.fromArray(verticalVector, normalVectors, offset)
+        Vec3.scale(verticalVector, verticalVector, height);
+
+        Vec3.fromArray(horizontalVector, binormalVectors, offset)
+        Vec3.scale(horizontalVector, horizontalVector, width);
+
+        Vec3.fromArray(positionVector, controlPoints, offset)
+
+        Vec3.add(p1, Vec3.add(p1, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p2, Vec3.add(p2, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p3, Vec3.sub(p3, positionVector, horizontalVector), verticalVector)
+        Vec3.add(p4, Vec3.sub(p4, positionVector, horizontalVector), verticalVector)
+
+        ChunkedArray.add3(vertices, p1[0], p1[1], p1[2])
+        ChunkedArray.add3(vertices, p2[0], p2[1], p2[2])
+        ChunkedArray.add3(vertices, p3[0], p3[1], p3[2])
+        ChunkedArray.add3(vertices, p4[0], p4[1], p4[2])
+
+        Vec3.cross(normalVector, horizontalVector, verticalVector)
+
+        for (let i = 0; i < 4; ++i) {
+            ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2])
+        }
+
+        ChunkedArray.add3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
+        ChunkedArray.add3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
+    }
+
+    return (linearSegments + 1) * 8 + (startCap ? (arrowHeight === 0 ? 4 : 8) : 0) + (endCap && arrowHeight === 0 ? 4 : 0)
 }

+ 87 - 10
src/mol-geo/primitive/tube.ts

@@ -7,17 +7,21 @@
 
 import { Vec3 } from 'mol-math/linear-algebra';
 import { ChunkedArray } from 'mol-data/util';
+import { MeshBuilderState } from '../shape/mesh-builder';
 
 const normalVector = Vec3.zero()
 const binormalVector = Vec3.zero()
+const controlPoint = Vec3.zero()
 const tempPos = Vec3.zero()
 const a = Vec3.zero()
 const b = Vec3.zero()
 const u = Vec3.zero()
 const v = Vec3.zero()
 
-export function addTube(controlPoints: Helpers.NumberArray, normalVectors: Helpers.NumberArray, binormalVectors: Helpers.NumberArray, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, vertices: ChunkedArray<number, 3>, normals: ChunkedArray<number, 3>, indices: ChunkedArray<number, 3>, ids: ChunkedArray<number, 1>, currentId: number) {
-    const vertexCount = vertices.elementCount
+export function addTube(controlPoints: Helpers.NumberArray, normalVectors: Helpers.NumberArray, binormalVectors: Helpers.NumberArray, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean, state: MeshBuilderState) {
+    const { vertices, normals, indices } = state
+
+    let vertexCount = vertices.elementCount
     const di = 1 / linearSegments
 
     for (let i = 0; i <= linearSegments; ++i) {
@@ -30,7 +34,7 @@ export function addTube(controlPoints: Helpers.NumberArray, normalVectors: Helpe
         const w = ff * width, h = ff * height;
 
         for (let j = 0; j < radialSegments; ++j) {
-            let t = 2 * Math.PI * j / radialSegments;
+            const t = 2 * Math.PI * j / radialSegments;
 
             Vec3.copy(a, u)
             Vec3.copy(b, v)
@@ -54,7 +58,6 @@ export function addTube(controlPoints: Helpers.NumberArray, normalVectors: Helpe
 
             ChunkedArray.add3(vertices, tempPos[0], tempPos[1], tempPos[2]);
             ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
-            ChunkedArray.add(ids, currentId);
         }
     }
 
@@ -62,16 +65,90 @@ export function addTube(controlPoints: Helpers.NumberArray, normalVectors: Helpe
         for (let j = 0; j < radialSegments; ++j) {
             ChunkedArray.add3(
                 indices,
-                (vertexCount + i * radialSegments + (j + 1) % radialSegments),
-                (vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments),
-                (vertexCount + i * radialSegments + j)
+                vertexCount + i * radialSegments + (j + 1) % radialSegments,
+                vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
+                vertexCount + i * radialSegments + j
             );
             ChunkedArray.add3(
                 indices,
-                (vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments),
-                (vertexCount + (i + 1) * radialSegments + j),
-                (vertexCount + i * radialSegments + j)
+                vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
+                vertexCount + (i + 1) * radialSegments + j,
+                vertexCount + i * radialSegments + j
             );
         }
     }
+
+    if (startCap) {
+        const offset = 0
+        vertexCount = vertices.elementCount
+        Vec3.fromArray(u, normalVectors, offset)
+        Vec3.fromArray(v, binormalVectors, offset)
+        Vec3.fromArray(controlPoint, controlPoints, offset)
+        Vec3.cross(normalVector, u, v)
+
+        ChunkedArray.add3(vertices, controlPoint[0], controlPoint[1], controlPoint[2]);
+        ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+        for (let i = 0; i < radialSegments; ++i) {
+            const t = 2 * Math.PI * i / radialSegments;
+
+            Vec3.copy(a, u)
+            Vec3.copy(b, v)
+            Vec3.add(
+                tempPos,
+                Vec3.scale(a, a, height * Math.cos(t)),
+                Vec3.scale(b, b, width * Math.sin(t))
+            )
+            Vec3.add(tempPos, controlPoint, tempPos)
+
+            ChunkedArray.add3(vertices, tempPos[0], tempPos[1], tempPos[2]);
+            ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+            ChunkedArray.add3(
+                indices,
+                vertexCount,
+                vertexCount + i + 1,
+                vertexCount + (i + 1) % radialSegments + 1
+            );
+        }
+    }
+
+    if (endCap) {
+        const offset = linearSegments * 3
+        vertexCount = vertices.elementCount
+        Vec3.fromArray(u, normalVectors, offset)
+        Vec3.fromArray(v, binormalVectors, offset)
+        Vec3.fromArray(controlPoint, controlPoints, offset)
+        Vec3.cross(normalVector, u, v)
+
+        ChunkedArray.add3(vertices, controlPoint[0], controlPoint[1], controlPoint[2]);
+        ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+        for (let i = 0; i < radialSegments; ++i) {
+            const t = 2 * Math.PI * i / radialSegments;
+
+            Vec3.copy(a, u)
+            Vec3.copy(b, v)
+            Vec3.add(
+                tempPos,
+                Vec3.scale(a, a, height * Math.cos(t)),
+                Vec3.scale(b, b, width * Math.sin(t))
+            )
+            Vec3.add(tempPos, controlPoint, tempPos)
+
+            ChunkedArray.add3(vertices, tempPos[0], tempPos[1], tempPos[2]);
+            ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+            if (i < radialSegments - 2) {
+                ChunkedArray.add3(
+                    indices,
+                    vertexCount + i + 1,
+                    vertexCount + (i + 1) % radialSegments + 1,
+                    vertexCount
+                );
+            }
+        }
+    }
+
+    return (linearSegments + 1) * radialSegments + (startCap ? radialSegments + 1 : 0) + (endCap ? radialSegments + 1 : 0)
 }

+ 4 - 4
src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts

@@ -191,14 +191,14 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Me
 
         // TODO size theme
         if (SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)) {
-            width = 1.0; height = 0.15
-            const arrowWidth = v.secStrucChange ? 1.7 : 0
-            builder.addSheet(controlPoints, normalVectors, binormalVectors, linearSegments, width, height, arrowWidth)
+            width = 0.15; height = 1.0
+            const arrowHeight = v.secStrucChange ? 1.7 : 0
+            builder.addSheet(controlPoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, true, true)
         } else {
             if (SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix)) {
                 width = 0.2; height = 1.0
             }
-            builder.addTube(controlPoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1)
+            builder.addTube(controlPoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, true, true)
         }
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {

+ 18 - 13
src/mol-geo/shape/mesh-builder.ts

@@ -16,12 +16,18 @@ import { getNormalMatrix } from '../util';
 import { addSheet } from '../primitive/sheet';
 import { addTube } from '../primitive/tube';
 
-type Primitive = {
+interface Primitive {
     vertices: Float32Array
     normals: Float32Array
     indices: Uint32Array
 }
 
+export interface MeshBuilderState {
+    vertices: ChunkedArray<number, 3>
+    normals: ChunkedArray<number, 3>
+    indices: ChunkedArray<number, 3>
+}
+
 export interface MeshBuilder {
     add(t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices?: Uint32Array): void
     addBox(t: Mat4, props?: BoxProps): void
@@ -29,8 +35,8 @@ export interface MeshBuilder {
     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
-    addTube(controlPoints: Helpers.NumberArray, torsionVectors: Helpers.NumberArray, normalVectors: Helpers.NumberArray, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number): void
-    addSheet(controlPoints: Helpers.NumberArray, normalVectors: Helpers.NumberArray, binormalVectors: Helpers.NumberArray, linearSegments: number, width: number, height: number, arrowWidth: number): void
+    addTube(centers: Helpers.NumberArray, normals: Helpers.NumberArray, binormals: Helpers.NumberArray, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean): void
+    addSheet(centers: Helpers.NumberArray, normals: Helpers.NumberArray, binormals: Helpers.NumberArray, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean): void
     setId(id: number): void
     getMesh(): Mesh
 }
@@ -56,10 +62,7 @@ function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number) {
     // 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
+    return Mat4.setTranslation(m, tmpCylinderCenter)
 }
 
 function getCylinder(props: CylinderProps) {
@@ -75,8 +78,7 @@ function getCylinder(props: CylinderProps) {
 const tmpIcosahedronMat = Mat4.identity()
 
 function setIcosahedronMat(m: Mat4, center: Vec3) {
-    Mat4.setTranslation(m, center)
-    return m
+    return Mat4.setTranslation(m, center)
 }
 
 function getIcosahedron(props: IcosahedronProps) {
@@ -96,6 +98,7 @@ export namespace MeshBuilder {
         const vertices = ChunkedArray.create(Float32Array, 3, chunkSize, mesh ? mesh.vertexBuffer.ref.value : initialCount);
         const normals = ChunkedArray.create(Float32Array, 3, chunkSize, mesh ? mesh.normalBuffer.ref.value : initialCount);
         const indices = ChunkedArray.create(Uint32Array, 3, chunkSize * 3, mesh ? mesh.indexBuffer.ref.value : initialCount * 3);
+        const state: MeshBuilderState = { vertices, normals, indices };
 
         const ids = ChunkedArray.create(Float32Array, 1, chunkSize, mesh ? mesh.idBuffer.ref.value : initialCount);
         const offsets = ChunkedArray.create(Uint32Array, 1, chunkSize, mesh ? mesh.offsetBuffer.ref.value : initialCount);
@@ -179,11 +182,13 @@ export namespace MeshBuilder {
                 setIcosahedronMat(tmpIcosahedronMat, center)
                 add(tmpIcosahedronMat, vertices, normals, indices)
             },
-            addTube: (controlPoints: Helpers.NumberArray, normalVectors: Helpers.NumberArray, binormalVectors: Helpers.NumberArray, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number) => {
-                addTube(controlPoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, waveFactor, vertices, normals, indices, ids, currentId)
+            addTube: (centers: Helpers.NumberArray, normals: Helpers.NumberArray, binormals: Helpers.NumberArray, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean) => {
+                const addedVertexCount = addTube(centers, normals, binormals, linearSegments, radialSegments, width, height, waveFactor, startCap, endCap, state)
+                for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(ids, currentId);
             },
-            addSheet: (controlPoints: Helpers.NumberArray, normalVectors: Helpers.NumberArray, binormalVectors: Helpers.NumberArray, linearSegments: number, width: number, height: number, arrowWidth: number) => {
-                addSheet(controlPoints, normalVectors, binormalVectors, linearSegments, width, height, arrowWidth, vertices, normals, indices, ids, currentId)
+            addSheet: (controls: Helpers.NumberArray, normals: Helpers.NumberArray, binormals: Helpers.NumberArray, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean) => {
+                const addedVertexCount = addSheet(controls, normals, binormals, linearSegments, width, height, arrowHeight, startCap, endCap, state)
+                for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(ids, currentId);
             },
             setId: (id: number) => {
                 if (currentId !== id) {

+ 11 - 1
src/mol-math/linear-algebra/3d/vec3.ts

@@ -194,6 +194,16 @@ namespace Vec3 {
         return Vec3.scale(out, Vec3.normalize(out, a), l)
     }
 
+    /**
+     * Negates the components of a vec3
+     */
+    export function negate(out: Vec3, a: Vec3) {
+        out[0] = -a[0];
+        out[1] = -a[1];
+        out[2] = -a[2];
+        return out;
+    }
+
     /**
      * Returns the inverse of the components of a Vec3
      */
@@ -384,7 +394,7 @@ namespace Vec3 {
     export function exactEquals(a: Vec3, b: Vec3) {
         return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
     }
-    
+
     /**
      * Returns whether or not the vectors have approximately the same elements in the same position.
      */

+ 2 - 2
src/mol-view/stage.ts

@@ -82,8 +82,8 @@ export class Stage {
         // this.loadPdbid('3j3q') // ...
         // this.loadPdbid('3sn6') // discontinuous chains
         // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
-        this.loadMmcifUrl(`../../examples/1cbs_updated.cif`)
-        // this.loadMmcifUrl(`../../examples/1crn.cif`)
+        // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`)
+        this.loadMmcifUrl(`../../examples/1crn.cif`)
 
         // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) // ok
         // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000002.cif`) // ok