Browse Source

refactored mesh-builder

Alexander Rose 6 years ago
parent
commit
0b1209a791

+ 5 - 6
src/mol-canvas3d/helper/bounding-sphere-helper.ts

@@ -17,24 +17,23 @@ export class BoundingSphereHelper {
     private renderObject: MeshRenderObject
 
     constructor(private scene: Scene, visible: boolean) {
-        const builder = MeshBuilder.create(1024, 512)
-        this.mesh = builder.getMesh()
+        this.mesh = MeshBuilder.getMesh(MeshBuilder.createState(1024, 512))
         const values = Mesh.createValuesSimple(this.mesh, { alpha: 0.1, doubleSided: false })
         this.renderObject = createMeshRenderObject(values, { visible, pickable: false, opaque: false })
         scene.add(this.renderObject)
     }
 
     update() {
-        const builder = MeshBuilder.create(1024, 512, this.mesh)
+        const builderState = MeshBuilder.createState(1024, 512, this.mesh)
         if (this.scene.boundingSphere.radius) {
-            addSphere(builder, this.scene.boundingSphere.center, this.scene.boundingSphere.radius, 2)
+            addSphere(builderState, this.scene.boundingSphere.center, this.scene.boundingSphere.radius, 2)
         }
         this.scene.forEach(r => {
             if (r.boundingSphere.radius) {
-                addSphere(builder, r.boundingSphere.center, r.boundingSphere.radius, 2)
+                addSphere(builderState, r.boundingSphere.center, r.boundingSphere.radius, 2)
             }
         })
-        this.mesh = builder.getMesh()
+        this.mesh = MeshBuilder.getMesh(builderState)
         ValueCell.update(this.renderObject.values.drawCount, Geometry.getDrawCount(this.mesh))
     }
 

+ 20 - 20
src/mol-geo/geometry/mesh/builder/bounding-box.ts

@@ -15,7 +15,7 @@ const tmpStart = Vec3.zero()
 const tmpEnd = Vec3.zero()
 const cylinderProps: CylinderProps = {}
 
-export function addBoundingBox(builder: MeshBuilder, box: Box3D, radius: number, detail: number, radialSegments: number) {
+export function addBoundingBox(state: MeshBuilder.State, box: Box3D, radius: number, detail: number, radialSegments: number) {
     const { min, max } = box
 
     cylinderProps.radiusTop = radius
@@ -23,43 +23,43 @@ export function addBoundingBox(builder: MeshBuilder, box: Box3D, radius: number,
     cylinderProps.radialSegments = radialSegments
 
     Vec3.set(tmpStart, max[0], max[1], max[2])
-    addSphere(builder, tmpStart, radius, detail)
+    addSphere(state, tmpStart, radius, detail)
     Vec3.set(tmpEnd, max[0], max[1], min[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
     Vec3.set(tmpEnd, max[0], min[1], max[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
     Vec3.set(tmpEnd, min[0], max[1], max[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
 
     Vec3.set(tmpStart, min[0], min[1], min[2])
-    addSphere(builder, tmpStart, radius, detail)
+    addSphere(state, tmpStart, radius, detail)
     Vec3.set(tmpEnd, min[0], min[1], max[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
     Vec3.set(tmpEnd, min[0], max[1], min[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
     Vec3.set(tmpEnd, max[0], min[1], min[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
 
     Vec3.set(tmpStart, max[0], min[1], min[2])
-    addSphere(builder, tmpStart, radius, detail)
+    addSphere(state, tmpStart, radius, detail)
     Vec3.set(tmpEnd, max[0], min[1], max[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
     Vec3.set(tmpEnd, max[0], max[1], min[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
 
     Vec3.set(tmpStart, min[0], min[1], max[2])
-    addSphere(builder, tmpStart, radius, detail)
+    addSphere(state, tmpStart, radius, detail)
     Vec3.set(tmpEnd, min[0], max[1], max[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
     Vec3.set(tmpEnd, max[0], min[1], max[2])
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
 
     Vec3.set(tmpStart, min[0], max[1], min[2])
-    addSphere(builder, tmpStart, radius, detail)
+    addSphere(state, tmpStart, radius, detail)
     Vec3.set(tmpEnd, max[0], max[1], min[2])
-    addSphere(builder, tmpEnd, radius, detail)
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addSphere(state, tmpEnd, radius, detail)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
     Vec3.set(tmpEnd, min[0], max[1], max[2])
-    addSphere(builder, tmpEnd, radius, detail)
-    addCylinder(builder, tmpStart, tmpEnd, 1, cylinderProps)
+    addSphere(state, tmpEnd, radius, detail)
+    addCylinder(state, tmpStart, tmpEnd, 1, cylinderProps)
 }

+ 7 - 7
src/mol-geo/geometry/mesh/builder/cylinder.ts

@@ -40,15 +40,15 @@ function getCylinder(props: CylinderProps) {
     return cylinder
 }
 
-export function addCylinder(builder: MeshBuilder, start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps) {
+export function addCylinder(state: MeshBuilder.State, start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps) {
     const d = Vec3.distance(start, end) * lengthScale
     props.height = d
     Vec3.sub(tmpCylinderDir, end, start)
     setCylinderMat(tmpCylinderMat, start, tmpCylinderDir, d)
-    builder.add(tmpCylinderMat, getCylinder(props))
+    MeshBuilder.addPrimitive(state, tmpCylinderMat, getCylinder(props))
 }
 
-export function addDoubleCylinder(builder: MeshBuilder, start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps) {
+export function addDoubleCylinder(state: MeshBuilder.State, start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps) {
     const d = Vec3.distance(start, end) * lengthScale
     props.height = d
     const cylinder = getCylinder(props)
@@ -56,14 +56,14 @@ export function addDoubleCylinder(builder: MeshBuilder, start: Vec3, end: Vec3,
     // positivly shifted cylinder
     Vec3.add(tmpCylinderStart, start, shift)
     setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
-    builder.add(tmpCylinderMat, cylinder)
+    MeshBuilder.addPrimitive(state, tmpCylinderMat, cylinder)
     // negativly shifted cylinder
     Vec3.sub(tmpCylinderStart, start, shift)
     setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
-    builder.add(tmpCylinderMat, cylinder)
+    MeshBuilder.addPrimitive(state, tmpCylinderMat, cylinder)
 }
 
-export function addFixedCountDashedCylinder(builder: MeshBuilder, start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps) {
+export function addFixedCountDashedCylinder(state: MeshBuilder.State, start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps) {
     const s = Math.floor(segmentCount / 2)
     const step = 1 / segmentCount
 
@@ -84,6 +84,6 @@ export function addFixedCountDashedCylinder(builder: MeshBuilder, start: Vec3, e
         Vec3.setMagnitude(tmpCylinderDir, tmpCylinderDir, d * f)
         Vec3.add(tmpCylinderStart, start, tmpCylinderDir)
         setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d * step)
-        builder.add(tmpCylinderMat, cylinder)
+        MeshBuilder.addPrimitive(state, tmpCylinderMat, cylinder)
     }
 }

+ 10 - 10
src/mol-geo/geometry/mesh/builder/sheet.ts

@@ -27,8 +27,8 @@ const p2 = Vec3.zero()
 const p3 = Vec3.zero()
 const p4 = Vec3.zero()
 
-function addCap(offset: number, builder: MeshBuilder, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number) {
-    const { vertices, normals, indices } = builder.state
+function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number) {
+    const { vertices, normals, indices } = state
     const vertexCount = vertices.elementCount
 
     Vec3.fromArray(verticalLeftVector, normalVectors, offset)
@@ -62,8 +62,8 @@ function addCap(offset: number, builder: MeshBuilder, controlPoints: ArrayLike<n
 }
 
 /** set arrowHeight = 0 for no arrow */
-export function addSheet(builder: MeshBuilder, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean) {
-    const { currentGroup, vertices, normals, indices, groups } = builder.state
+export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean) {
+    const { currentGroup, vertices, normals, indices, groups } = state
 
     let vertexCount = vertices.elementCount
     let offsetLength = 0
@@ -150,18 +150,18 @@ export function addSheet(builder: MeshBuilder, controlPoints: ArrayLike<number>,
 
     if (startCap) {
         const h = arrowHeight === 0 ? height : arrowHeight
-        addCap(0, builder, controlPoints, normalVectors, binormalVectors, width, h, h)
+        addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h)
     } else if (arrowHeight > 0) {
-        addCap(0, builder, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height)
-        addCap(0, builder, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height)
+        addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height)
+        addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height)
     }
 
     if (endCap && arrowHeight === 0) {
-        addCap(linearSegments * 3, builder, controlPoints, normalVectors, binormalVectors, width, height, height)
+        addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, height, height)
     }
 
-    const addedVertexCount = (linearSegments + 1) * 8 + 
-        (startCap ? 4 : (arrowHeight > 0 ? 8 : 0)) + 
+    const addedVertexCount = (linearSegments + 1) * 8 +
+        (startCap ? 4 : (arrowHeight > 0 ? 8 : 0)) +
         (endCap && arrowHeight === 0 ? 4 : 0)
     for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(groups, currentGroup)
 }

+ 2 - 2
src/mol-geo/geometry/mesh/builder/sphere.ts

@@ -25,6 +25,6 @@ function getSphere(detail: number) {
     return sphere
 }
 
-export function addSphere(builder: MeshBuilder, center: Vec3, radius: number, detail: number) {
-    builder.add(setSphereMat(tmpSphereMat, center, radius), getSphere(detail))
+export function addSphere(state: MeshBuilder.State, center: Vec3, radius: number, detail: number) {
+    MeshBuilder.addPrimitive(state, setSphereMat(tmpSphereMat, center, radius), getSphere(detail))
 }

+ 2 - 2
src/mol-geo/geometry/mesh/builder/tube.ts

@@ -27,8 +27,8 @@ function add3AndScale2(out: Vec3, a: Vec3, b: Vec3, c: Vec3, sa: number, sb: num
     out[2] = (a[2] * sa) + (b[2] * sb) + c[2];
 }
 
-export function addTube(builder: MeshBuilder, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean) {
-    const { currentGroup, vertices, normals, indices, groups } = builder.state
+export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean) {
+    const { currentGroup, vertices, normals, indices, groups } = state
 
     let vertexCount = vertices.elementCount
     const di = 1 / linearSegments

+ 50 - 57
src/mol-geo/geometry/mesh/mesh-builder.ts

@@ -11,72 +11,65 @@ import { Mesh } from './mesh';
 import { getNormalMatrix } from '../../util';
 import { Primitive } from '../../primitive/primitive';
 
-export interface MeshBuilderState {
-    readonly currentGroup: number
-    readonly vertices: ChunkedArray<number, 3>
-    readonly normals: ChunkedArray<number, 3>
-    readonly indices: ChunkedArray<number, 3>
-    readonly groups: ChunkedArray<number, 1>
-}
-
-export interface MeshBuilder {
-    state: MeshBuilderState
-    add(t: Mat4, primitive: Primitive): void
-    setGroup(id: number): void
-    getMesh(): Mesh
-}
-
 const tmpV = Vec3.zero()
 const tmpMat3 = Mat3.zero()
 
 export namespace MeshBuilder {
-    export function create(initialCount = 2048, chunkSize = 1024, mesh?: Mesh): 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 groups = ChunkedArray.create(Float32Array, 1, chunkSize, mesh ? mesh.groupBuffer.ref.value : initialCount);
+    export interface State {
+        currentGroup: number
+        readonly vertices: ChunkedArray<number, 3>
+        readonly normals: ChunkedArray<number, 3>
+        readonly indices: ChunkedArray<number, 3>
+        readonly groups: ChunkedArray<number, 1>
+        readonly mesh?: Mesh
+    }
 
-        let currentGroup = -1
+    export function createState(initialCount = 2048, chunkSize = 1024, mesh?: Mesh): State {
+        return {
+            currentGroup: -1,
+            vertices: ChunkedArray.create(Float32Array, 3, chunkSize, mesh ? mesh.vertexBuffer.ref.value : initialCount),
+            normals: ChunkedArray.create(Float32Array, 3, chunkSize, mesh ? mesh.normalBuffer.ref.value : initialCount),
+            indices: ChunkedArray.create(Uint32Array, 3, chunkSize * 3, mesh ? mesh.indexBuffer.ref.value : initialCount * 3),
+            groups: ChunkedArray.create(Float32Array, 1, chunkSize, mesh ? mesh.groupBuffer.ref.value : initialCount),
+            mesh
+        }
+    }
 
-        function add(t: Mat4, primitive: Primitive) {
-            const { vertices: va, normals: na, indices: ia } = primitive
-            const offset = vertices.elementCount
-            const n = getNormalMatrix(tmpMat3, t)
-            for (let i = 0, il = va.length; i < il; i += 3) {
-                // position
-                Vec3.transformMat4(tmpV, Vec3.fromArray(tmpV, va, i), t)
-                ChunkedArray.add3(vertices, tmpV[0], tmpV[1], tmpV[2]);
-                // normal
-                Vec3.transformMat3(tmpV, Vec3.fromArray(tmpV, na, i), n)
-                ChunkedArray.add3(normals, tmpV[0], tmpV[1], tmpV[2]);
-                // group
-                ChunkedArray.add(groups, currentGroup);
-            }
-            for (let i = 0, il = ia.length; i < il; i += 3) {
-                ChunkedArray.add3(indices, ia[i] + offset, ia[i + 1] + offset, ia[i + 2] + offset);
-            }
+    export function addPrimitive(state: State, t: Mat4, primitive: Primitive) {
+        const { vertices: va, normals: na, indices: ia } = primitive
+        const { vertices, normals, indices, groups, currentGroup } = state
+        const offset = vertices.elementCount
+        const n = getNormalMatrix(tmpMat3, t)
+        for (let i = 0, il = va.length; i < il; i += 3) {
+            // position
+            Vec3.transformMat4(tmpV, Vec3.fromArray(tmpV, va, i), t)
+            ChunkedArray.add3(vertices, tmpV[0], tmpV[1], tmpV[2]);
+            // normal
+            Vec3.transformMat3(tmpV, Vec3.fromArray(tmpV, na, i), n)
+            ChunkedArray.add3(normals, tmpV[0], tmpV[1], tmpV[2]);
+            // group
+            ChunkedArray.add(groups, currentGroup);
         }
+        for (let i = 0, il = ia.length; i < il; i += 3) {
+            ChunkedArray.add3(indices, ia[i] + offset, ia[i + 1] + offset, ia[i + 2] + offset);
+        }
+    }
 
+    export function getMesh (state: State): Mesh {
+        const { vertices, normals, indices, groups, mesh } = state
+        const vb = ChunkedArray.compact(vertices, true) as Float32Array
+        const ib = ChunkedArray.compact(indices, true) as Uint32Array
+        const nb = ChunkedArray.compact(normals, true) as Float32Array
+        const gb = ChunkedArray.compact(groups, true) as Float32Array
         return {
-            state: { get currentGroup() { return currentGroup }, vertices, normals, indices, groups },
-            add,
-            setGroup: (group: number) => { currentGroup = group },
-            getMesh: () => {
-                const vb = ChunkedArray.compact(vertices, true) as Float32Array
-                const ib = ChunkedArray.compact(indices, true) as Uint32Array
-                const nb = ChunkedArray.compact(normals, true) as Float32Array
-                const gb = ChunkedArray.compact(groups, true) as Float32Array
-                return {
-                    kind: 'mesh',
-                    vertexCount: vertices.elementCount,
-                    triangleCount: indices.elementCount,
-                    vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb),
-                    indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib),
-                    normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb),
-                    groupBuffer: mesh ? ValueCell.update(mesh.groupBuffer, gb) : ValueCell.create(gb),
-                    normalsComputed: true,
-                }
-            }
+            kind: 'mesh',
+            vertexCount: state.vertices.elementCount,
+            triangleCount: state.indices.elementCount,
+            vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb),
+            indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib),
+            normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb),
+            groupBuffer: mesh ? ValueCell.update(mesh.groupBuffer, gb) : ValueCell.create(gb),
+            normalsComputed: true,
         }
     }
 }

+ 21 - 21
src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts

@@ -45,7 +45,7 @@ const pentagonalPrism = PentagonalPrism()
 const hexagonalPrism = HexagonalPrism()
 
 function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateSymbolParams>, mesh?: Mesh) {
-    const builder = MeshBuilder.create(256, 128, mesh)
+    const builderState = MeshBuilder.createState(256, 128, mesh)
 
     const { detail, sizeFactor } = props
 
@@ -68,77 +68,77 @@ function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure,
         Mat4.targetTo(t, center, pd, normal)
         Mat4.setTranslation(t, center)
 
-        builder.setGroup(i * 2)
+        builderState.currentGroup = i * 2
 
         switch (shapeType) {
             case SaccharideShapes.FilledSphere:
-                addSphere(builder, center, radius, detail)
+                addSphere(builderState, center, radius, detail)
                 break;
             case SaccharideShapes.FilledCube:
                 Mat4.scaleUniformly(t, t, side)
-                builder.add(t, box)
+                MeshBuilder.addPrimitive(builderState, t, box)
                 break;
             case SaccharideShapes.CrossedCube:
                 Mat4.scaleUniformly(t, t, side)
-                builder.add(t, perforatedBox)
+                MeshBuilder.addPrimitive(builderState, t, perforatedBox)
                 Mat4.mul(t, t, Mat4.rotZ90X180)
-                builder.setGroup(i * 2 + 1)
-                builder.add(t, perforatedBox)
+                builderState.currentGroup = i * 2 + 1
+                MeshBuilder.addPrimitive(builderState, t, perforatedBox)
                 break;
             case SaccharideShapes.FilledCone:
                 Mat4.scaleUniformly(t, t, side * 1.2)
-                builder.add(t, octagonalPyramid)
+                MeshBuilder.addPrimitive(builderState, t, octagonalPyramid)
                 break
             case SaccharideShapes.DevidedCone:
                 Mat4.scaleUniformly(t, t, side * 1.2)
-                builder.add(t, perforatedOctagonalPyramid)
+                MeshBuilder.addPrimitive(builderState, t, perforatedOctagonalPyramid)
                 Mat4.mul(t, t, Mat4.rotZ90)
-                builder.setGroup(i * 2 + 1)
-                builder.add(t, perforatedOctagonalPyramid)
+                builderState.currentGroup = i * 2 + 1
+                MeshBuilder.addPrimitive(builderState, t, perforatedOctagonalPyramid)
                 break
             case SaccharideShapes.FlatBox:
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
-                builder.add(t, box)
+                MeshBuilder.addPrimitive(builderState, t, box)
                 break
             case SaccharideShapes.FilledStar:
                 Mat4.scaleUniformly(t, t, side)
                 Mat4.mul(t, t, Mat4.rotZY90)
-                builder.add(t, star)
+                MeshBuilder.addPrimitive(builderState, t, star)
                 break
             case SaccharideShapes.FilledDiamond:
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
-                builder.add(t, octahedron)
+                MeshBuilder.addPrimitive(builderState, t, octahedron)
                 break
             case SaccharideShapes.DividedDiamond:
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
-                builder.add(t, perforatedOctahedron)
+                MeshBuilder.addPrimitive(builderState, t, perforatedOctahedron)
                 Mat4.mul(t, t, Mat4.rotY90)
-                builder.setGroup(i * 2 + 1)
-                builder.add(t, perforatedOctahedron)
+                builderState.currentGroup = i * 2 + 1
+                MeshBuilder.addPrimitive(builderState, t, perforatedOctahedron)
                 break
             case SaccharideShapes.FlatDiamond:
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side / 2, side / 2))
-                builder.add(t, diamondPrism)
+                MeshBuilder.addPrimitive(builderState, t, diamondPrism)
                 break
             case SaccharideShapes.Pentagon:
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
-                builder.add(t, pentagonalPrism)
+                MeshBuilder.addPrimitive(builderState, t, pentagonalPrism)
                 break
             case SaccharideShapes.FlatHexagon:
             default:
                 Mat4.mul(t, t, Mat4.rotZYZ90)
                 Mat4.scale(t, t, Vec3.set(sVec, side / 1.5, side , side / 2))
-                builder.add(t, hexagonalPrism)
+                MeshBuilder.addPrimitive(builderState, t, hexagonalPrism)
                 break
         }
     }
 
-    return builder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export const CarbohydrateSymbolParams = {

+ 7 - 6
src/mol-repr/structure/visual/nucleotide-block-mesh.ts

@@ -51,7 +51,7 @@ function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: St
     const { sizeFactor, radialSegments } = props
 
     const vertexCount = nucleotideElementCount * (box.vertices.length / 3 + radialSegments * 2)
-    const builder = MeshBuilder.create(vertexCount, vertexCount / 4, mesh)
+    const builderState = MeshBuilder.createState(vertexCount, vertexCount / 4, mesh)
 
     const { elements, model } = unit
     const { modifiedResidues } = model.properties
@@ -99,8 +99,8 @@ function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: St
 
                 if (idx5 !== -1 && idx6 !== -1) {
                     pos(idx5, p5); pos(idx6, p6)
-                    builder.setGroup(i)
-                    addCylinder(builder, p5, p6, 1, cylinderProps)
+                    builderState.currentGroup = i
+                    addCylinder(builderState, p5, p6, 1, cylinderProps)
                     if (idx1 !== -1 && idx2 !== -1 && idx3 !== -1 && idx4 !== -1) {
                         pos(idx1, p1); pos(idx2, p2); pos(idx3, p3); pos(idx4, p4);
                         Vec3.normalize(v12, Vec3.sub(v12, p2, p1))
@@ -110,7 +110,7 @@ function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: St
                         Vec3.scaleAndAdd(center, p1, v12, height / 2 - 0.2)
                         Mat4.scale(t, t, Vec3.set(sVec, width, depth, height))
                         Mat4.setTranslation(t, center)
-                        builder.add(t, box)
+                        MeshBuilder.addPrimitive(builderState, t, box)
                     }
                 }
 
@@ -119,7 +119,7 @@ function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: St
         }
     }
 
-    return builder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export const NucleotideBlockParams = {
@@ -137,7 +137,8 @@ export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockParams> {
         mark: markNucleotideElement,
         setUpdateState: (state: VisualUpdateState, newProps: PD.Values<NucleotideBlockParams>, currentProps: PD.Values<NucleotideBlockParams>) => {
             state.createGeometry = (
-                newProps.sizeFactor !== currentProps.sizeFactor
+                newProps.sizeFactor !== currentProps.sizeFactor ||
+                newProps.radialSegments !== currentProps.radialSegments
             )
         }
     })

+ 6 - 6
src/mol-repr/structure/visual/polymer-backbone-cylinder.ts

@@ -35,7 +35,7 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
     const { radialSegments, sizeFactor } = props
 
     const vertexCountEstimate = radialSegments * 2 * polymerElementCount * 2
-    const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh)
+    const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 10, mesh)
 
     const { elements } = unit
     const pos = unit.conformation.invariantPosition
@@ -50,15 +50,15 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
         pos(centerB.element, pB)
 
         cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA) * sizeFactor
-        builder.setGroup(OrderedSet.indexOf(elements, centerA.element))
-        addCylinder(builder, pA, pB, 0.5, cylinderProps)
+        builderState.currentGroup = OrderedSet.indexOf(elements, centerA.element)
+        addCylinder(builderState, pA, pB, 0.5, cylinderProps)
 
         cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB) * sizeFactor
-        builder.setGroup(OrderedSet.indexOf(elements, centerB.element))
-        addCylinder(builder, pB, pA, 0.5, cylinderProps)
+        builderState.currentGroup = OrderedSet.indexOf(elements, centerB.element)
+        addCylinder(builderState, pB, pA, 0.5, cylinderProps)
     }
 
-    return builder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export const PolymerBackboneParams = {

+ 4 - 4
src/mol-repr/structure/visual/polymer-direction-wedge.ts

@@ -43,7 +43,7 @@ function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, structu
     const { sizeFactor } = props
 
     const vertexCount = polymerElementCount * 24
-    const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh)
+    const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh)
     const linearSegments = 1
 
     const state = createCurveSegmentState(linearSegments)
@@ -53,7 +53,7 @@ function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, structu
     const polymerTraceIt = PolymerTraceIterator(unit)
     while (polymerTraceIt.hasNext) {
         const v = polymerTraceIt.move()
-        builder.setGroup(i)
+        builderState.currentGroup = i
 
         const isNucleicType = isNucleic(v.moleculeType)
         const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)
@@ -77,13 +77,13 @@ function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, structu
             Mat4.mul(t, t, Mat4.rotY90Z180)
             Mat4.scale(t, t, Vec3.set(sVec, height, width, depth))
             Mat4.setTranslation(t, v.p2)
-            builder.add(t, wedge)
+            MeshBuilder.addPrimitive(builderState, t, wedge)
         }
 
         ++i
     }
 
-    return builder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export const PolymerDirectionParams = {

+ 8 - 8
src/mol-repr/structure/visual/polymer-gap-cylinder.ts

@@ -36,7 +36,7 @@ function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure:
     const { sizeFactor, radialSegments } = props
 
     const vertexCountEstimate = segmentCount * radialSegments * 2 * polymerGapCount * 2
-    const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh)
+    const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 10, mesh)
 
     const pos = unit.conformation.invariantPosition
     const pA = Vec3.zero()
@@ -50,26 +50,26 @@ function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure:
     while (polymerGapIt.hasNext) {
         const { centerA, centerB } = polymerGapIt.move()
         if (centerA.element === centerB.element) {
-            builder.setGroup(i)
+            builderState.currentGroup = i
             pos(centerA.element, pA)
-            addSphere(builder, pA, 0.6, 0)
+            addSphere(builderState, pA, 0.6, 0)
         } else {
             pos(centerA.element, pA)
             pos(centerB.element, pB)
 
             cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA) * sizeFactor
-            builder.setGroup(i)
-            addFixedCountDashedCylinder(builder, pA, pB, 0.5, segmentCount, cylinderProps)
+            builderState.currentGroup = i
+            addFixedCountDashedCylinder(builderState, pA, pB, 0.5, segmentCount, cylinderProps)
 
             cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB) * sizeFactor
-            builder.setGroup(i + 1)
-            addFixedCountDashedCylinder(builder, pB, pA, 0.5, segmentCount, cylinderProps)
+            builderState.currentGroup = i + 1
+            addFixedCountDashedCylinder(builderState, pB, pA, 0.5, segmentCount, cylinderProps)
         }
 
         i += 2
     }
 
-    return builder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export const InterUnitLinkParams = {

+ 5 - 5
src/mol-repr/structure/visual/polymer-trace-mesh.ts

@@ -37,7 +37,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
     const { sizeFactor, linearSegments, radialSegments, aspectRatio, arrowFactor } = props
 
     const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2
-    const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh)
+    const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh)
 
     const isCoarse = Unit.isCoarse(unit)
     const state = createCurveSegmentState(linearSegments)
@@ -47,7 +47,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
     const polymerTraceIt = PolymerTraceIterator(unit)
     while (polymerTraceIt.hasNext) {
         const v = polymerTraceIt.move()
-        builder.setGroup(i)
+        builderState.currentGroup = i
 
         const isNucleicType = isNucleic(v.moleculeType)
         const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)
@@ -63,7 +63,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
         if (isSheet) {
             const height = width * aspectRatio
             const arrowHeight = v.secStrucLast ? height * arrowFactor : 0
-            addSheet(builder, curvePoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, v.secStrucFirst, v.secStrucLast)
+            addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, v.secStrucFirst, v.secStrucLast)
         } else {
             let height: number
             if (isHelix) {
@@ -74,13 +74,13 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
             } else {
                 height = width
             }
-            addTube(builder, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, v.secStrucFirst, v.secStrucLast)
+            addTube(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, v.secStrucFirst, v.secStrucLast)
         }
 
         ++i
     }
 
-    return builder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export const PolymerTraceParams = {

+ 4 - 4
src/mol-repr/structure/visual/util/element.ts

@@ -29,7 +29,7 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
     const { elements } = unit;
     const elementCount = elements.length;
     const vertexCount = elementCount * sphereVertexCount(detail)
-    const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
+    const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
 
     const v = Vec3.zero()
     const pos = unit.conformation.invariantPosition
@@ -40,11 +40,11 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
         l.element = elements[i]
         pos(elements[i], v)
 
-        meshBuilder.setGroup(i)
-        addSphere(meshBuilder, v, theme.size.size(l) * sizeFactor, detail)
+        builderState.currentGroup = i
+        addSphere(builderState, v, theme.size.size(l) * sizeFactor, detail)
     }
 
-    return meshBuilder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export function markElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {

+ 7 - 7
src/mol-repr/structure/visual/util/link.ts

@@ -74,7 +74,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkCyli
     const { linkScale, linkSpacing, radialSegments } = props
 
     const vertexCountEstimate = radialSegments * 2 * linkCount * 2
-    const meshBuilder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 4, mesh)
+    const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh)
 
     const va = Vec3.zero()
     const vb = Vec3.zero()
@@ -87,12 +87,12 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkCyli
         const linkRadius = radius(edgeIndex)
         const o = order(edgeIndex)
         const f = flags(edgeIndex)
-        meshBuilder.setGroup(edgeIndex)
+        builderState.currentGroup = edgeIndex
 
         if (LinkType.is(f, LinkType.Flag.MetallicCoordination) || LinkType.is(f, LinkType.Flag.Hydrogen)) {
             // show metall coordinations and hydrogen bonds with dashed cylinders
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius / 3
-            addFixedCountDashedCylinder(meshBuilder, va, vb, 0.5, 7, cylinderProps)
+            addFixedCountDashedCylinder(builderState, va, vb, 0.5, 7, cylinderProps)
         } else if (o === 2 || o === 3) {
             // show bonds with order 2 or 3 using 2 or 3 parallel cylinders
             const multiRadius = linkRadius * (linkScale / (0.5 * o))
@@ -103,15 +103,15 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkCyli
 
             cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius
 
-            if (o === 3) addCylinder(meshBuilder, va, vb, 0.5, cylinderProps)
-            addDoubleCylinder(meshBuilder, va, vb, 0.5, vShift, cylinderProps)
+            if (o === 3) addCylinder(builderState, va, vb, 0.5, cylinderProps)
+            addDoubleCylinder(builderState, va, vb, 0.5, vShift, cylinderProps)
         } else {
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius
-            addCylinder(meshBuilder, va, vb, 0.5, cylinderProps)
+            addCylinder(builderState, va, vb, 0.5, cylinderProps)
         }
     }
 
-    return meshBuilder.getMesh()
+    return MeshBuilder.getMesh(builderState)
 }
 
 export namespace LinkIterator {