diff --git a/src/mol-data/int/impl/segmentation.ts b/src/mol-data/int/impl/segmentation.ts
index f3e7413f97c11d597eae86d519285c418c28671d..99e7114cb7b6419c830a3c0c2acbbd4bac6c3907 100644
--- a/src/mol-data/int/impl/segmentation.ts
+++ b/src/mol-data/int/impl/segmentation.ts
@@ -52,18 +52,22 @@ export function projectValue({ segments }: Segmentation, set: OrderedSet, value:
 }
 
 export class SegmentIterator<T extends number = number> implements Iterator<Segs.Segment<T>> {
-    private segmentMin = 0;
-    private segmentMax = 0;
+    private _segmentMin = 0;
+    private _segmentMax = 0;
+    private segmentCur = 0;
     private setRange = Interval.Empty;
     private value: Segs.Segment<T> = { index: 0, start: 0 as T, end: 0 as T };
 
     hasNext: boolean = false;
 
+    get segmentMin() { return this._segmentMin }
+    get segmentMax() { return this._segmentMax }
+
     move() {
         while (this.hasNext) {
             if (this.updateValue()) {
-                this.value.index = this.segmentMin++;
-                this.hasNext = this.segmentMax >= this.segmentMin && Interval.size(this.setRange) > 0;
+                this.value.index = this.segmentCur++;
+                this.hasNext = this._segmentMax >= this.segmentCur && Interval.size(this.setRange) > 0;
                 break;
             } else {
                 this.updateSegmentRange();
@@ -73,7 +77,7 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs
     }
 
     private updateValue() {
-        const segmentEnd = this.segments[this.segmentMin + 1];
+        const segmentEnd = this.segments[this.segmentCur + 1];
         // TODO: add optimized version for interval and array?
         const setEnd = OrderedSet.findPredecessorIndexInInterval(this.set, segmentEnd, this.setRange);
         this.value.start = Interval.start(this.setRange) as T;
@@ -89,10 +93,11 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs
             return;
         }
 
-        this.segmentMin = this.segmentMap[OrderedSet.getAt(this.set, sMin)];
-        this.segmentMax = this.segmentMap[OrderedSet.getAt(this.set, sMax)];
+        this.segmentCur = this.segmentMap[OrderedSet.getAt(this.set, sMin)];
+        this._segmentMax = this.segmentMap[OrderedSet.getAt(this.set, sMax)];
 
-        this.hasNext = this.segmentMax >= this.segmentMin;
+        this._segmentMin = this.segmentCur
+        this.hasNext = this._segmentMax >= this.segmentCur;
     }
 
     setSegment(segment: Segs.Segment<T>) {
diff --git a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts
index f0bfe33559d7fbaaca324f441706e32d4e9614c0..1866005b5125027c6bc3dbc8ccead530164dd60b 100644
--- a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts
+++ b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts
@@ -26,17 +26,11 @@ import { MeshBuilder } from '../../../shape/mesh-builder';
 import { getPolymerElementCount, PolymerTraceIterator } from './util/polymer';
 import { Vec3 } from 'mol-math/linear-algebra';
 
-// export function spline(target: THREE.Vector3, p1: THREE.Vector3, p2: THREE.Vector3, p3: THREE.Vector3, t: number) {
-//     let a = Math.pow(1 - t, 2) / 2;
-//     let c = Math.pow(t, 2) / 2;
-//     let b = 1 - a - c;
-
-//     let x = a * p1.x + b * p2.x + c * p3.x;
-//     let y = a * p1.y + b * p2.y + c * p3.y;
-//     let z = a * p1.z + b * p2.z + c * p3.z;
-
-//     target.set(x, y, z);
-// }
+export function reflect(target: Vec3, p1: Vec3, p2: Vec3, amount: number) {
+    target[0] = p1[0] - amount * (p2[0] - p1[0])
+    target[1] = p1[1] - amount * (p2[1] - p1[1])
+    target[2] = p1[2] - amount * (p2[2] - p1[2])
+}
 
 async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
@@ -45,34 +39,150 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Me
 
     // TODO better vertex count estimates
     const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh)
-    const linearSegmentCount = 10
+    const linearSegments = 5
+    const radialSegments = 8
+    const tension = 0.9
+
+    const tA = Vec3.zero()
+    const tB = Vec3.zero()
+    const dA = Vec3.zero()
+    const dB = Vec3.zero()
+    const torsionVec = Vec3.zero()
+    const initialTorsionVec = Vec3.zero()
+    const tangentVec = Vec3.zero()
+    const normalVec = Vec3.zero()
 
-    const v0 = Vec3.zero()
-    const v1 = Vec3.zero()
+    const tmp = Vec3.zero()
+    const reflectedControlPoint = Vec3.zero()
+
+    const pn = (linearSegments + 1) * 3
+    const controlPoints = new Float32Array(pn)
+    const torsionVectors = new Float32Array(pn)
+    const normalVectors = new Float32Array(pn)
 
     let i = 0
     const polymerTraceIt = PolymerTraceIterator(unit)
     while (polymerTraceIt.hasNext) {
         const v = polymerTraceIt.move()
+        builder.setId(v.index)
 
-        Vec3.spline(v1, v.c0, v.c1, v.c2, v.c3, 0.5, 0.5)
+        Vec3.spline(tB, v.t1, v.t2, v.t3, v.t4, 0.5, tension)
+        Vec3.spline(dA, v.d12, v.d23, v.d34, v.d45, 0.5, tension)
 
-        builder.setId(v.index)
-        for (let j = 1; j <= linearSegmentCount; ++j) {
-            let t = j * 1.0 / linearSegmentCount;
-            Vec3.copy(v0, v1)
+        Vec3.normalize(initialTorsionVec, Vec3.sub(initialTorsionVec, tB, dB))
+
+        Vec3.toArray(tB, controlPoints, 0)
+        Vec3.normalize(torsionVec, Vec3.sub(torsionVec, tB, dB))
+        Vec3.toArray(torsionVec, torsionVectors, 0)
+        // approximate tangent as direction to previous control point
+        Vec3.normalize(tangentVec, Vec3.sub(tangentVec, tB, tA))
+        Vec3.normalize(normalVec, Vec3.cross(normalVec, tangentVec, torsionVec))
+        Vec3.toArray(normalVec, normalVectors, 0)
+
+        //
+
+        const t12 = Vec3.zero()
+        const t23 = Vec3.zero()
+        const t34 = Vec3.zero()
+        const t45 = Vec3.zero()
+        Vec3.spline(t12, v.t0, v.t1, v.t2, v.t3, 0.5, tension)
+        Vec3.spline(t23, v.t1, v.t2, v.t3, v.t4, 0.5, tension)
+        Vec3.spline(t34, v.t2, v.t3, v.t4, v.t5, 0.5, tension)
+        Vec3.spline(t45, v.t3, v.t4, v.t5, v.t6, 0.5, tension)
+
+        // const dp12 = Vec3.zero()
+        // const dp23 = Vec3.zero()
+        // const dp34 = Vec3.zero()
+        // const dp45 = Vec3.zero()
+        // Vec3.projectPointOnVector(dp12, v.d12, t12, v.t1)
+        // Vec3.projectPointOnVector(dp23, v.d23, t23, v.t2)
+        // Vec3.projectPointOnVector(dp34, v.d34, t34, v.t3)
+        // Vec3.projectPointOnVector(dp45, v.d45, t45, v.t4)
+
+        const td12 = Vec3.zero()
+        const td23 = Vec3.zero()
+        const td34 = Vec3.zero()
+        const td45 = Vec3.zero()
+        Vec3.normalize(td12, Vec3.sub(td12, t12, v.d12))
+        Vec3.scaleAndAdd(v.d12, t12, td12, 1)
+        Vec3.normalize(td23, Vec3.sub(td23, t23, v.d23))
+        if (Vec3.dot(td12, td23) < 0) {
+            Vec3.scaleAndAdd(v.d23, t23, td23, -1)
+            console.log('foo td0 td1')
+        } else {
+            Vec3.scaleAndAdd(v.d23, t23, td23, 1)
+        }
+        Vec3.normalize(td34, Vec3.sub(td34, t34, v.d34))
+        if (Vec3.dot(td12, td34) < 0) {
+            Vec3.scaleAndAdd(v.d34, t34, td34, -1)
+            console.log('foo td1 td2')
+        } else {
+            Vec3.scaleAndAdd(v.d34, t34, td34, 1)
+        }
+        Vec3.normalize(td45, Vec3.sub(td45, t45, v.d45))
+        if (Vec3.dot(td12, td45) < 0) {
+            Vec3.scaleAndAdd(v.d45, t45, td45, -1)
+            console.log('foo td2 td3')
+        } else {
+            Vec3.scaleAndAdd(v.d45, t45, td45, 1)
+        }
+
+        // console.log(td0, td1, td2, td3)
+
+        builder.addIcosahedron(t12, 0.3, 1)
+        builder.addIcosahedron(t23, 0.3, 1)
+        builder.addIcosahedron(t34, 0.3, 1)
+        builder.addIcosahedron(t45, 0.3, 1)
+
+        // builder.addIcosahedron(dp12, 0.3, 1)
+        // builder.addIcosahedron(dp23, 0.3, 1)
+        // builder.addIcosahedron(dp34, 0.3, 1)
+        // builder.addIcosahedron(dp45, 0.3, 1)
+
+        builder.addIcosahedron(v.d12, 0.3, 1)
+        builder.addIcosahedron(v.d23, 0.3, 1)
+        builder.addIcosahedron(v.d34, 0.3, 1)
+        builder.addIcosahedron(v.d45, 0.3, 1)
+
+        for (let j = 1; j <= linearSegments; ++j) {
+            const t = j * 1.0 / linearSegments;
+            Vec3.copy(tA, tB)
             // if ((v.last && t > 0.5) || (v.first && t < 0.5)) break
+
             if (t < 0.5) {
-                Vec3.spline(v1, v.c0, v.c1, v.c2, v.c3, t + 0.5, 0.5)
+                Vec3.spline(tB, v.t1, v.t2, v.t3, v.t4, t + 0.5, tension)
             } else {
-                Vec3.spline(v1, v.c1, v.c2, v.c3, v.c4, t - 0.5, 0.5)
+                Vec3.spline(tB, v.t2, v.t3, v.t4, v.t5, t - 0.5, tension)
             }
+            Vec3.spline(dB, v.d12, v.d23, v.d34, v.d45, t, tension)
+
+            // reflect(reflectedControlPoint, tB, tA, 1)
+            Vec3.toArray(tB, controlPoints, j * 3)
+
+            Vec3.normalize(torsionVec, Vec3.sub(torsionVec, tB, dB))
+            // if (Vec3.dot(initialTorsionVec, torsionVec) < 0) Vec3.scale(torsionVec, torsionVec, -1)
+            Vec3.toArray(torsionVec, torsionVectors, j * 3)
+
+            // approximate tangent as direction to previous control point
+            Vec3.normalize(tangentVec, Vec3.sub(tangentVec, tB, tA))
+            Vec3.normalize(normalVec, Vec3.cross(normalVec, tangentVec, torsionVec))
+            Vec3.toArray(normalVec, normalVectors, j * 3)
+
             // TODO size theme
-            builder.addCylinder(v0, v1, 1.0, { radiusTop: 0.1, radiusBottom: 0.1 })
+            // builder.addCylinder(tA, tB, 1.0, { radiusTop: 0.3, radiusBottom: 0.3 })
+
+            builder.addIcosahedron(dB, 0.1, 1)
+
+            builder.addCylinder(tB, Vec3.add(tmp, tB, torsionVec), 1.0, { radiusTop: 0.1, radiusBottom: 0.1 })
+            // builder.addCylinder(tB, Vec3.add(tmp, tB, normalVec), 1.0, { radiusTop: 0.1, radiusBottom: 0.1 })
+
+            console.log(tA, tB)
         }
 
+        builder.addTube(controlPoints, torsionVectors, normalVectors, linearSegments, radialSegments)
+
         if (i % 10000 === 0 && ctx.shouldUpdate) {
-            await ctx.update({ message: 'Backbone mesh', current: i, max: polymerElementCount });
+            await ctx.update({ message: 'Polymer trace mesh', current: i, max: polymerElementCount });
         }
         ++i
     }
diff --git a/src/mol-geo/representation/structure/visual/util/polymer.ts b/src/mol-geo/representation/structure/visual/util/polymer.ts
index 0381dc63b08bdefc3c028b2be77049423d05cf18..0141f8b5b4511e0fee8c37e53da1a313f4a11061 100644
--- a/src/mol-geo/representation/structure/visual/util/polymer.ts
+++ b/src/mol-geo/representation/structure/visual/util/polymer.ts
@@ -5,7 +5,7 @@
  */
 
 import { Unit, Element, StructureProperties, Model } from 'mol-model/structure';
-import { Segmentation } from 'mol-data/int';
+import { Segmentation, Interval } from 'mol-data/int';
 import { MoleculeType } from 'mol-model/structure/model/types';
 import Iterator from 'mol-data/iterator';
 import { SegmentIterator } from 'mol-data/int/impl/segmentation';
@@ -83,8 +83,12 @@ function getTraceName2(model: Model, residueModelIndex: number) {
     let traceName = ''
     if (moleculeType === MoleculeType.protein) {
         traceName = 'CA'
-    } else if (moleculeType === MoleculeType.DNA || moleculeType === MoleculeType.RNA) {
-        traceName = 'P'
+    } else if (moleculeType === MoleculeType.DNA) {
+        // traceName = 'P'
+        traceName = 'C3\''
+    } else if (moleculeType === MoleculeType.RNA) {
+        // traceName = 'P'
+        traceName = 'C4\''
     }
     return traceName
 }
@@ -99,6 +103,32 @@ function getTraceElement2(model: Model, residueModelSegment: Segmentation.Segmen
     return residueModelSegment.start
 }
 
+function getDirectionName2(model: Model, residueModelIndex: number) {
+    const compId = model.atomicHierarchy.residues.label_comp_id.value(residueModelIndex)
+    const chemCompMap = model.properties.chemicalComponentMap
+    const cc = chemCompMap.get(compId)
+    const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown
+    let traceName = ''
+    if (moleculeType === MoleculeType.protein) {
+        traceName = 'O'
+    } else if (moleculeType === MoleculeType.DNA) {
+        traceName = 'O4\''
+    } else if (moleculeType === MoleculeType.RNA) {
+        traceName = 'C3\''
+    }
+    return traceName
+}
+
+function getDirectionElement2(model: Model, residueModelSegment: Segmentation.Segment<Element>) {
+    const traceName = getDirectionName2(model, residueModelSegment.index)
+
+    for (let j = residueModelSegment.start, _j = residueModelSegment.end; j < _j; j++) {
+        if (model.atomicHierarchy.atoms.label_atom_id.value(j) === traceName) return j
+    }
+    console.log('direction name element not found', { ...residueModelSegment })
+    return residueModelSegment.start
+}
+
 
 
 /** Iterates over consecutive pairs of residues/coarse elements in polymers */
@@ -268,8 +298,8 @@ export class CoarsePolymerBackboneIterator<T extends number = number> implements
 
 
 /**
- * Iterates over individual residues/coarse elements in polymers while providing information
- * about the neighbourhood in the underlying model for drawing splines
+ * Iterates over individual residues/coarse elements in polymers of a unit while
+ * providing information about the neighbourhood in the underlying model for drawing splines
  */
 export function PolymerTraceIterator(unit: Unit): Iterator<PolymerTraceElement> {
     switch (unit.kind) {
@@ -285,51 +315,63 @@ interface PolymerTraceElement {
     index: number
     first: boolean
     last: boolean
-    c0: Vec3
-    c1: Vec3
-    c2: Vec3
-    c3: Vec3
-    c4: Vec3
+
+    t0: Vec3
+    t1: Vec3
+    t2: Vec3
+    t3: Vec3
+    t4: Vec3
+    t5: Vec3
+    t6: Vec3
+
+    d12: Vec3
+    d23: Vec3
+    d34: Vec3
+    d45: Vec3
 }
 
-function createPolymerTraceElement (unit: Unit) {
+function createPolymerTraceElement (unit: Unit): PolymerTraceElement {
     return {
         center: Element.Location(unit),
         index: 0,
         first: false,
         last: false,
-        c0: Vec3.zero(),
-        c1: Vec3.zero(),
-        c2: Vec3.zero(),
-        c3: Vec3.zero(),
-        c4: Vec3.zero()
+
+        t0: Vec3.zero(),
+        t1: Vec3.zero(),
+        t2: Vec3.zero(),
+        t3: Vec3.zero(),
+        t4: Vec3.zero(),
+        t5: Vec3.zero(),
+        t6: Vec3.zero(),
+
+        d12: Vec3.zero(),
+        d23: Vec3.zero(),
+        d34: Vec3.zero(),
+        d45: Vec3.zero(),
     }
 }
 
 const enum AtomicPolymerTraceIteratorState { nextPolymer, nextResidue }
 
-function setSegment (outSegment: Segmentation.Segment<Element>, index: number, segments: Segmentation<Element>, boundingSegment: Segmentation.Segment<Element>): Segmentation.Segment<Element> {
-    index = Math.min(Math.max(0, index), segments.segments.length - 2)
-    outSegment.index = index
-    outSegment.start = segments.segments[index]
-    outSegment.end = segments.segments[index + 1]
+function setSegment (outSegment: Segmentation.Segment<Element>, index: number, segments: Segmentation<Element>, min: number, max: number): Segmentation.Segment<Element> {
+    // index = Math.min(Math.max(0, index), segments.segments.length - 2)
+    const _index = Math.min(Math.max(min, index), max)
+    if (isNaN(_index)) console.log(_index, index, min, max)
+    outSegment.index = _index
+    outSegment.start = segments.segments[_index]
+    outSegment.end = segments.segments[_index + 1]
+    // console.log(index, {...outSegment}, {...boundingSegment}, segments.segments[boundingSegment.index])
     return outSegment
 }
 
-// const p0 = 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()
-
 export class AtomicPolymerTraceIterator<T extends number = number> implements Iterator<PolymerTraceElement> {
     private value: PolymerTraceElement
 
     private polymerIt: SegmentIterator<Element>
     private residueIt: SegmentIterator<Element>
-    private polymerSegment: Segmentation.Segment<Element>
+    private residueSegmentMin: number
+    private residueSegmentMax: number
     private state: AtomicPolymerTraceIteratorState = AtomicPolymerTraceIteratorState.nextPolymer
     private residueSegments: Segmentation<Element>
 
@@ -345,42 +387,62 @@ export class AtomicPolymerTraceIterator<T extends number = number> implements It
         target[2] = this.unit.model.atomicConformation.z[index]
     }
 
+    updateResidueSegmentRange(polymerSegment: Segmentation.Segment<Element>) {
+        const { polymerSegments, residueSegments } = this.unit.model.atomicHierarchy
+        const sMin = polymerSegments.segments[polymerSegment.index]
+        const sMax = polymerSegments.segments[polymerSegment.index + 1] - 1
+        this.residueSegmentMin = residueSegments.segmentMap[sMin]
+        this.residueSegmentMax = residueSegments.segmentMap[sMax]
+    }
+
     move() {
         const { residueIt, polymerIt, value } = this
-        value.first = false
-        value.last = false
 
         if (this.state === AtomicPolymerTraceIteratorState.nextPolymer) {
             if (polymerIt.hasNext) {
-                this.polymerSegment = polymerIt.move();
-                residueIt.setSegment(this.polymerSegment);
+                const polymerSegment = polymerIt.move();
+                residueIt.setSegment(polymerSegment);
+                this.updateResidueSegmentRange(polymerSegment)
                 this.state = AtomicPolymerTraceIteratorState.nextResidue
-                value.first = true
             }
         }
 
         if (this.state === AtomicPolymerTraceIteratorState.nextResidue) {
+            const { tmpSegment, residueSegments, residueSegmentMin, residueSegmentMax } = this
             const residueSegment = residueIt.move();
+            const resSegIdx = residueSegment.index
             value.index = setTraceElement(value.center, residueSegment)
 
-            setSegment(this.tmpSegment, residueSegment.index - 2, this.residueSegments, this.polymerSegment)
-            this.pos(value.c0, getTraceElement2(this.unit.model, this.tmpSegment))
+            setSegment(tmpSegment, resSegIdx - 3, residueSegments, residueSegmentMin, residueSegmentMax)
+            this.pos(value.t0, getTraceElement2(this.unit.model, tmpSegment))
+
+            setSegment(tmpSegment, resSegIdx - 2, residueSegments, residueSegmentMin, residueSegmentMax)
+            this.pos(value.t1, getTraceElement2(this.unit.model, tmpSegment))
+            this.pos(value.d12, getDirectionElement2(this.unit.model, tmpSegment))
+
+            setSegment(tmpSegment, resSegIdx - 1, residueSegments, residueSegmentMin, residueSegmentMax)
+            this.pos(value.t2, getTraceElement2(this.unit.model, tmpSegment))
+            this.pos(value.d23, getDirectionElement2(this.unit.model, tmpSegment))
+
+            setSegment(tmpSegment, resSegIdx, residueSegments, residueSegmentMin, residueSegmentMax)
+            this.pos(value.t3, getTraceElement2(this.unit.model, tmpSegment))
+            this.pos(value.d34, getDirectionElement2(this.unit.model, tmpSegment))
 
-            setSegment(this.tmpSegment, residueSegment.index - 1, this.residueSegments, this.polymerSegment)
-            this.pos(value.c1, getTraceElement2(this.unit.model, this.tmpSegment))
+            setSegment(tmpSegment, resSegIdx + 1, residueSegments, residueSegmentMin, residueSegmentMax)
+            this.pos(value.t4, getTraceElement2(this.unit.model, tmpSegment))
+            this.pos(value.d45, getDirectionElement2(this.unit.model, tmpSegment))
 
-            setSegment(this.tmpSegment, residueSegment.index, this.residueSegments, this.polymerSegment)
-            this.pos(value.c2, getTraceElement2(this.unit.model, this.tmpSegment))
+            setSegment(tmpSegment, resSegIdx + 2, residueSegments, residueSegmentMin, residueSegmentMax)
+            this.pos(value.t5, getTraceElement2(this.unit.model, tmpSegment))
 
-            setSegment(this.tmpSegment, residueSegment.index + 1, this.residueSegments, this.polymerSegment)
-            this.pos(value.c3, getTraceElement2(this.unit.model, this.tmpSegment))
+            setSegment(tmpSegment, resSegIdx + 3, residueSegments, residueSegmentMin, residueSegmentMax)
+            this.pos(value.t6, getTraceElement2(this.unit.model, tmpSegment))
 
-            setSegment(this.tmpSegment, residueSegment.index + 2, this.residueSegments, this.polymerSegment)
-            this.pos(value.c4, getTraceElement2(this.unit.model, this.tmpSegment))
+            value.first = resSegIdx === residueSegmentMin
+            value.last = resSegIdx === residueSegmentMax
 
             if (!residueIt.hasNext) {
                 this.state = AtomicPolymerTraceIteratorState.nextPolymer
-                value.last = true
             }
         }
 
diff --git a/src/mol-geo/shape/mesh-builder.ts b/src/mol-geo/shape/mesh-builder.ts
index 6eed0f441cc4772eb606763c1f731e67880c964c..a7f9cf3a2463ed1fec0443d902efdcf7ebd181a0 100644
--- a/src/mol-geo/shape/mesh-builder.ts
+++ b/src/mol-geo/shape/mesh-builder.ts
@@ -27,6 +27,7 @@ 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): void
     setId(id: number): void
     getMesh(): Mesh
 }
@@ -175,6 +176,84 @@ export namespace MeshBuilder {
                 setIcosahedronMat(tmpIcosahedronMat, center)
                 add(tmpIcosahedronMat, vertices, normals, indices)
             },
+            addTube: (controlPoints: Helpers.NumberArray, torsionVectors: Helpers.NumberArray, normalVectors: Helpers.NumberArray, linearSegments: number, radialSegments: number) => {
+                console.log(controlPoints, torsionVectors, normalVectors, linearSegments, radialSegments)
+
+                const ico = getIcosahedron({ radius: 0.1, detail: 1 })
+
+                const radialVector = Vec3.zero()
+                const normalVector = Vec3.zero()
+                const tempPos = Vec3.zero()
+                const a = Vec3.zero()
+                const b = Vec3.zero()
+                const u = Vec3.zero()
+                const v = Vec3.zero()
+
+                const waveFactor = 1
+                const width = 0.6
+                const height = 0.2
+
+                const vertexCount = vertices.elementCount
+                const di = 1 / linearSegments
+
+                for (let i = 0; i <= linearSegments; ++i) {
+                    const i3 = i * 3
+                    Vec3.fromArray(u, torsionVectors, i3)
+                    Vec3.fromArray(v, normalVectors, i3)
+
+                    const tt = di * i - 0.5;
+                    const ff = 1 + (waveFactor - 1) * (Math.cos(2 * Math.PI * tt) + 1);
+                    const w = ff * width, h = ff * height;
+
+                    for (let j = 0; j < radialSegments; ++j) {
+                        let t = 2 * Math.PI * j / radialSegments;
+
+                        Vec3.copy(a, u)
+                        Vec3.copy(b, v)
+                        Vec3.add(
+                            radialVector,
+                            Vec3.scale(a, a, w * Math.cos(t)),
+                            Vec3.scale(b, b, h * Math.sin(t))
+                        )
+
+                        Vec3.copy(a, u)
+                        Vec3.copy(b, v)
+                        Vec3.add(
+                            normalVector,
+                            Vec3.scale(a, a, h * Math.cos(t)),
+                            Vec3.scale(b, b, w * Math.sin(t))
+                        )
+                        Vec3.normalize(normalVector, normalVector)
+
+                        Vec3.fromArray(tempPos, controlPoints, i3)
+                        Vec3.add(tempPos, tempPos, radialVector)
+
+                        ChunkedArray.add3(vertices, tempPos[0], tempPos[1], tempPos[2]);
+                        ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+                        ChunkedArray.add(ids, currentId);
+
+                        // setIcosahedronMat(tmpIcosahedronMat, tempPos)
+                        // add(tmpIcosahedronMat, ico.vertices, ico.normals, ico.indices)
+                    }
+                }
+
+                for (let i = 0; i < linearSegments; ++i) {
+                    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)
+                        );
+                        ChunkedArray.add3(
+                            indices,
+                            (vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments),
+                            (vertexCount + (i + 1) * radialSegments + j),
+                            (vertexCount + i * radialSegments + j)
+                        );
+                    }
+                }
+            },
             setId: (id: number) => {
                 if (currentId !== id) {
                     currentId = id
diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts
index 55c138fe3bbad7e1ef13dad567355e3ea01bd8a5..071ce1785a922e282f2aecb4d205872f4590ff08 100644
--- a/src/mol-math/linear-algebra/3d/vec3.ts
+++ b/src/mol-math/linear-algebra/3d/vec3.ts
@@ -380,6 +380,13 @@ namespace Vec3 {
         return v[0] === 0 && v[1] === 0 && v[2] === 0
     }
 
+    export function projectPointOnVector(out: Vec3, point: Vec3, vector: Vec3, origin: Vec3) {
+        // point.sub(origin).projectOnVector(vector).add(origin)
+        Vec3.sub(out, Vec3.copy(out, point), origin)
+        const scalar = Vec3.dot(vector, out) / Vec3.squaredMagnitude(vector);
+        return Vec3.add(out, Vec3.scale(out, Vec3.copy(out, vector), scalar), origin);
+    }
+
     export function toString(a: Vec3) {
         return `[${a[0]} ${a[1]} ${a[2]}]`;
     }
diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts
index 34c85ffca360ea256642a3d7cf13d14fdbb4122c..a7bdc552848e1883f007f4b967d424c6f22907c5 100644
--- a/src/mol-view/stage.ts
+++ b/src/mol-view/stage.ts
@@ -73,14 +73,14 @@ export class Stage {
 
         // this.loadPdbid('1jj2')
         // this.loadPdbid('4umt') // ligand has bond with order 3
-        // this.loadPdbid('1crn') // small
+        this.loadPdbid('1crn') // small
         // this.loadPdbid('1hrv') // viral assembly
         // this.loadPdbid('1rb8') // virus
         // this.loadPdbid('1blu') // metal coordination
         // this.loadPdbid('3pqr') // inter unit bonds
         // this.loadPdbid('4v5a') // ribosome
         // this.loadPdbid('3j3q') // ...
-        this.loadPdbid('3sn6') // discontinuous chains
+        // this.loadPdbid('3sn6') // discontinuous chains
         // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
 
         // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) // ok
@@ -103,7 +103,7 @@ export class Stage {
         const modelEntity = await MmcifUrlToModel.apply(this.ctx, urlEntity)
         const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity)
 
-        // StructureToBallAndStick.apply(this.ctx, structureEntity, { ...ballAndStickProps, visible: true })
+        StructureToBallAndStick.apply(this.ctx, structureEntity, { ...ballAndStickProps, visible: false })
         StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: false })
         StructureToDistanceRestraint.apply(this.ctx, structureEntity, { ...distanceRestraintProps, visible: false })
         // StructureToBackbone.apply(this.ctx, structureEntity, { ...backboneProps, visible: true })
@@ -111,13 +111,14 @@ export class Stage {
 
         this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });
 
-        const structureEntity2 = await ModelToStructure.apply(this.ctx, modelEntity)
-        const q1 = Q.generators.atoms({
-            residueTest: l => SP.residue.label_seq_id(l) > 30
-        });
-        structureEntity2.value = Selection.unionStructure(await Query(q1)(structureEntity2.value).run());
-        StructureToBackbone.apply(this.ctx, structureEntity2, { ...backboneProps, visible: true })
-        StructureToCartoon.apply(this.ctx, structureEntity2, { ...cartoonProps, visible: true })
+        // const structureEntity2 = await ModelToStructure.apply(this.ctx, modelEntity)
+        // const q1 = Q.generators.atoms({
+        //     residueTest: l => SP.residue.label_seq_id(l) < 10
+        // });
+        // structureEntity2.value = Selection.unionStructure(await Query(q1)(structureEntity2.value).run());
+        // StructureToBackbone.apply(this.ctx, structureEntity2, { ...backboneProps, visible: true })
+        // StructureToCartoon.apply(this.ctx, structureEntity2, { ...cartoonProps, visible: true })
+        // StructureToBallAndStick.apply(this.ctx, structureEntity2, { ...ballAndStickProps, visible: true })
     }
 
     loadPdbid (pdbid: string) {