Browse Source

refactored mesh-builder

Alexander Rose 6 years ago
parent
commit
26eaf9b458

+ 4 - 2
src/apps/canvas/index.ts

@@ -22,6 +22,8 @@ import { ShapeRepresentation } from 'mol-geo/representation/shape';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Shape } from 'mol-model/shape';
 import { Color } from 'mol-util/color';
+import { addSphere } from 'mol-geo/mesh/builder/sphere';
+import { Box } from 'mol-geo/primitive/box';
 
 const container = document.getElementById('container')
 if (!container) throw new Error('Can not find element with id "container".')
@@ -121,7 +123,7 @@ async function init() {
     meshBuilder.setGroup(0)
     colors[0] = Color(0xFF2233)
     labels[0] = 'red sphere'
-    meshBuilder.addSphere(Vec3.create(0, 0, 0), 4, 2)
+    addSphere(meshBuilder, Vec3.create(0, 0, 0), 4, 2)
     // green cube
     meshBuilder.setGroup(1)
     colors[1] = Color(0x2233FF)
@@ -129,7 +131,7 @@ async function init() {
     const t = Mat4.identity()
     Mat4.fromTranslation(t, Vec3.create(10, 0, 0))
     Mat4.scale(t, t, Vec3.create(3, 3, 3))
-    meshBuilder.addBox(t)
+    meshBuilder.add(t, Box())
     const mesh = meshBuilder.getMesh()
     // const mesh = getObjFromUrl('mesh.obj')
 

+ 89 - 0
src/mol-geo/mesh/builder/cylinder.ts

@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+import { MeshBuilder } from '../mesh-builder';
+import { Primitive } from '../../primitive/primitive';
+import { Cylinder, CylinderProps } from '../../primitive/cylinder';
+
+const cylinderMap = new Map<string, Primitive>()
+const up = Vec3.create(0, 1, 0)
+
+const tmpCylinderDir = Vec3.zero()
+const tmpCylinderMatDir = Vec3.zero()
+const tmpCylinderCenter = Vec3.zero()
+const tmpCylinderMat = Mat4.zero()
+const tmpCylinderStart = Vec3.zero()
+const tmpUp = Vec3.zero()
+
+function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number) {
+    Vec3.setMagnitude(tmpCylinderMatDir, dir, length / 2)
+    Vec3.add(tmpCylinderCenter, start, tmpCylinderMatDir)
+    // ensure the direction used to create the rotation is always pointing in the same
+    // direction so the triangles of adjacent cylinder will line up
+    Vec3.copy(tmpUp, up)
+    if (Vec3.dot(tmpCylinderMatDir, tmpUp) < 0) Vec3.scale(tmpUp, tmpUp, -1)
+    Vec3.makeRotation(m, tmpUp, tmpCylinderMatDir)
+    return Mat4.setTranslation(m, tmpCylinderCenter)
+}
+
+function getCylinder(props: CylinderProps) {
+    const key = JSON.stringify(props)
+    let cylinder = cylinderMap.get(key)
+    if (cylinder === undefined) {
+        cylinder = Cylinder(props)
+        cylinderMap.set(key, cylinder)
+    }
+    return cylinder
+}
+
+export function addCylinder(builder: MeshBuilder, 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))
+}
+
+export function addDoubleCylinder(builder: MeshBuilder, start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps) {
+    const d = Vec3.distance(start, end) * lengthScale
+    props.height = d
+    const cylinder = getCylinder(props)
+    Vec3.sub(tmpCylinderDir, end, start)
+    // positivly shifted cylinder
+    Vec3.add(tmpCylinderStart, start, shift)
+    setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
+    builder.add(tmpCylinderMat, cylinder)
+    // negativly shifted cylinder
+    Vec3.sub(tmpCylinderStart, start, shift)
+    setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
+    builder.add(tmpCylinderMat, cylinder)
+}
+
+export function addFixedCountDashedCylinder(builder: MeshBuilder, start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps) {
+    const s = Math.floor(segmentCount / 2)
+    const step = 1 / segmentCount
+
+    // automatically adjust length so links/bonds that are rendered as two half cylinders
+    // have evenly spaced dashed cylinders
+    if (lengthScale < 1) {
+        const bias = lengthScale / 2 / segmentCount
+        lengthScale += segmentCount % 2 === 1 ? bias : -bias
+    }
+
+    const d = Vec3.distance(start, end) * lengthScale
+    props.height = d * step
+    const cylinder = getCylinder(props)
+    Vec3.sub(tmpCylinderDir, end, start)
+
+    for (let j = 0; j < s; ++j) {
+        const f = step * (j * 2 + 1)
+        Vec3.setMagnitude(tmpCylinderDir, tmpCylinderDir, d * f)
+        Vec3.add(tmpCylinderStart, start, tmpCylinderDir)
+        setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d * step)
+        builder.add(tmpCylinderMat, cylinder)
+    }
+}

+ 5 - 4
src/mol-geo/mesh/sheet.ts → src/mol-geo/mesh/builder/sheet.ts

@@ -7,7 +7,7 @@
 
 import { Vec3 } from 'mol-math/linear-algebra';
 import { ChunkedArray } from 'mol-data/util';
-import { MeshBuilderState } from './mesh-builder';
+import { MeshBuilder } from '../mesh-builder';
 
 const tA = Vec3.zero()
 const tB = Vec3.zero()
@@ -25,8 +25,8 @@ const p2 = Vec3.zero()
 const p3 = Vec3.zero()
 const p4 = Vec3.zero()
 
-export function addSheet(controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean, state: MeshBuilderState) {
-    const { vertices, normals, indices } = state
+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
 
     let vertexCount = vertices.elementCount
     let offsetLength = 0
@@ -173,5 +173,6 @@ export function addSheet(controlPoints: ArrayLike<number>, normalVectors: ArrayL
         ChunkedArray.add3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
     }
 
-    return (linearSegments + 1) * 8 + (startCap ? 4 : 0) + (endCap && arrowHeight === 0 ? 4 : 0)
+    const addedVertexCount = (linearSegments + 1) * 8 + (startCap ? 4 : 0) + (endCap && arrowHeight === 0 ? 4 : 0)
+    for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(groups, currentGroup)
 }

+ 30 - 0
src/mol-geo/mesh/builder/sphere.ts

@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+import { MeshBuilder } from '../mesh-builder';
+import { Primitive } from '../../primitive/primitive';
+import { Sphere } from '../../primitive/sphere';
+
+const sphereMap = new Map<number, Primitive>()
+const tmpSphereMat = Mat4.identity()
+
+function setSphereMat(m: Mat4, center: Vec3, radius: number) {
+    return Mat4.scaleUniformly(m, Mat4.fromTranslation(m, center), radius)
+}
+
+function getSphere(detail: number) {
+    let sphere = sphereMap.get(detail)
+    if (sphere === undefined) {
+        sphere = Sphere(detail)
+        sphereMap.set(detail, sphere)
+    }
+    return sphere
+}
+
+export function addSphere(builder: MeshBuilder, center: Vec3, radius: number, detail: number) {
+    builder.add(setSphereMat(tmpSphereMat, center, radius), getSphere(detail))
+}

+ 6 - 5
src/mol-geo/mesh/tube.ts → src/mol-geo/mesh/builder/tube.ts

@@ -7,7 +7,7 @@
 
 import { Vec3 } from 'mol-math/linear-algebra';
 import { ChunkedArray } from 'mol-data/util';
-import { MeshBuilderState } from '../mesh/mesh-builder';
+import { MeshBuilder } from '../mesh-builder';
 
 const normalVector = Vec3.zero()
 const binormalVector = Vec3.zero()
@@ -18,8 +18,8 @@ const b = Vec3.zero()
 const u = Vec3.zero()
 const v = Vec3.zero()
 
-export function addTube(controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean, state: MeshBuilderState) {
-    const { vertices, normals, indices } = state
+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
 
     let vertexCount = vertices.elementCount
     const di = 1 / linearSegments
@@ -127,7 +127,7 @@ export function addTube(controlPoints: ArrayLike<number>, normalVectors: ArrayLi
 
         vertexCount = vertices.elementCount
         for (let i = 0; i < radialSegments; ++i) {
-            const t = 2 * Math.PI * i / radialSegments;
+            const t = 2 * Math.PI * i / radialSegments
 
             Vec3.copy(a, u)
             Vec3.copy(b, v)
@@ -150,5 +150,6 @@ export function addTube(controlPoints: ArrayLike<number>, normalVectors: ArrayLi
         }
     }
 
-    return (linearSegments + 1) * radialSegments + (startCap ? radialSegments + 1 : 0) + (endCap ? radialSegments + 1 : 0)
+    const addedVertexCount = (linearSegments + 1) * radialSegments + (startCap ? radialSegments + 1 : 0) + (endCap ? radialSegments + 1 : 0)
+    for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(groups, currentGroup)
 }

+ 11 - 170
src/mol-geo/mesh/mesh-builder.ts

@@ -7,119 +7,39 @@
 import { ValueCell } from 'mol-util/value-cell'
 import { Vec3, Mat4, Mat3 } from 'mol-math/linear-algebra';
 import { ChunkedArray } from 'mol-data/util';
-
-import { Plane } from '../primitive/plane';
-import { Cylinder, CylinderProps } from '../primitive/cylinder';
-import { Sphere } from '../primitive/sphere';
 import { Mesh } from './mesh';
 import { getNormalMatrix } from '../util';
-import { addSheet } from './sheet';
-import { addTube } from './tube';
-import { StarProps, Star } from '../primitive/star';
-import { Octahedron, PerforatedOctahedron } from '../primitive/octahedron';
 import { Primitive } from '../primitive/primitive';
-import { DiamondPrism, PentagonalPrism, HexagonalPrism } from '../primitive/prism';
-import { OctagonalPyramide, PerforatedOctagonalPyramid } from '../primitive/pyramid';
-import { PerforatedBox, Box } from '../primitive/box';
-import { Wedge } from '../primitive/wedge';
 
 export interface MeshBuilderState {
-    vertices: ChunkedArray<number, 3>
-    normals: ChunkedArray<number, 3>
-    indices: ChunkedArray<number, 3>
+    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 {
-    add(t: Mat4, _vertices: ArrayLike<number>, _normals: ArrayLike<number>, _indices?: ArrayLike<number>): void
-    addPrimitive(t: Mat4, primitive: Primitive): void,
-
-    addBox(t: Mat4): void
-    addPerforatedBox(t: Mat4): void
-    addPlane(t: Mat4): void
-    addWedge(t: Mat4): void
-    addDiamondPrism(t: Mat4): void
-    addPentagonalPrism(t: Mat4): void
-    addHexagonalPrism(t: Mat4): void
-    addOctagonalPyramid(t: Mat4): void
-    addPerforatedOctagonalPyramid(t: Mat4): void
-    addStar(t: Mat4, props?: StarProps): void
-    addOctahedron(t: Mat4): void
-    addPerforatedOctahedron(t: Mat4): void
-
-    addCylinder(start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps): void
-    addDoubleCylinder(start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps): void
-    addFixedCountDashedCylinder(start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps): void
-    addSphere(center: Vec3, radius: number, detail: number): void
-
-    addTube(centers: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean): void
-    addSheet(centers: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean): void
-
+    state: MeshBuilderState
+    add(t: Mat4, primitive: Primitive): void
     setGroup(id: number): void
     getMesh(): Mesh
 }
 
-const cylinderMap = new Map<string, Primitive>()
-const sphereMap = new Map<number, Primitive>()
-
-const up = Vec3.create(0, 1, 0)
 const tmpV = Vec3.zero()
 const tmpMat3 = Mat3.zero()
 
-const tmpCylinderDir = Vec3.zero()
-const tmpCylinderMatDir = Vec3.zero()
-const tmpCylinderCenter = Vec3.zero()
-const tmpCylinderMat = Mat4.zero()
-const tmpCylinderStart = Vec3.zero()
-const tmpUp = Vec3.zero()
-
-function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number) {
-    Vec3.setMagnitude(tmpCylinderMatDir, dir, length / 2)
-    Vec3.add(tmpCylinderCenter, start, tmpCylinderMatDir)
-    // ensure the direction used to create the rotation is always pointing in the same
-    // direction so the triangles of adjacent cylinder will line up
-    Vec3.copy(tmpUp, up)
-    if (Vec3.dot(tmpCylinderMatDir, tmpUp) < 0) Vec3.scale(tmpUp, tmpUp, -1)
-    Vec3.makeRotation(m, tmpUp, tmpCylinderMatDir)
-    return Mat4.setTranslation(m, tmpCylinderCenter)
-}
-
-function getCylinder(props: CylinderProps) {
-    const key = JSON.stringify(props)
-    let cylinder = cylinderMap.get(key)
-    if (cylinder === undefined) {
-        cylinder = Cylinder(props)
-        cylinderMap.set(key, cylinder)
-    }
-    return cylinder
-}
-
-const tmpSphereMat = Mat4.identity()
-
-function setSphereMat(m: Mat4, center: Vec3, radius: number) {
-    return Mat4.scaleUniformly(m, Mat4.fromTranslation(m, center), radius)
-}
-
-function getSphere(detail: number) {
-    let sphere = sphereMap.get(detail)
-    if (sphere === undefined) {
-        sphere = Sphere(detail)
-        sphereMap.set(detail, sphere)
-    }
-    return sphere
-}
-
 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 state: MeshBuilderState = { vertices, normals, indices };
-
         const groups = ChunkedArray.create(Float32Array, 1, chunkSize, mesh ? mesh.groupBuffer.ref.value : initialCount);
 
         let currentGroup = -1
 
-        function add(t: Mat4, va: ArrayLike<number>, na: ArrayLike<number>, ia: ArrayLike<number>) {
+        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) {
@@ -137,89 +57,10 @@ export namespace MeshBuilder {
             }
         }
 
-        function addPrimitive(t: Mat4, primitive: Primitive) {
-            const { vertices, normals, indices } = primitive
-            add(t, vertices, normals, indices)
-        }
-
         return {
+            state: { get currentGroup() { return currentGroup }, vertices, normals, indices, groups },
             add,
-            addPrimitive,
-
-            addBox: (t: Mat4) => addPrimitive(t, Box()),
-            addPerforatedBox: (t: Mat4) => addPrimitive(t, PerforatedBox()),
-            addPlane: (t: Mat4) => addPrimitive(t, Plane()),
-            addWedge: (t: Mat4) => addPrimitive(t, Wedge()),
-            addDiamondPrism: (t: Mat4) => addPrimitive(t, DiamondPrism()),
-            addPentagonalPrism: (t: Mat4) => addPrimitive(t, PentagonalPrism()),
-            addHexagonalPrism: (t: Mat4) => addPrimitive(t, HexagonalPrism()),
-            addOctagonalPyramid: (t: Mat4) => addPrimitive(t, OctagonalPyramide()),
-            addPerforatedOctagonalPyramid: (t: Mat4) => addPrimitive(t, PerforatedOctagonalPyramid()),
-            addStar: (t: Mat4, props?: StarProps) => addPrimitive(t, Star(props)),
-            addOctahedron: (t: Mat4) => addPrimitive(t, Octahedron()),
-            addPerforatedOctahedron: (t: Mat4) => addPrimitive(t, PerforatedOctahedron()),
-
-            addCylinder: (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)
-                addPrimitive(tmpCylinderMat, getCylinder(props))
-            },
-            addDoubleCylinder: (start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps) => {
-                const d = Vec3.distance(start, end) * lengthScale
-                props.height = d
-                const cylinder = getCylinder(props)
-                Vec3.sub(tmpCylinderDir, end, start)
-                // positivly shifted cylinder
-                Vec3.add(tmpCylinderStart, start, shift)
-                setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
-                addPrimitive(tmpCylinderMat, cylinder)
-                // negativly shifted cylinder
-                Vec3.sub(tmpCylinderStart, start, shift)
-                setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
-                addPrimitive(tmpCylinderMat, cylinder)
-            },
-            addFixedCountDashedCylinder: (start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps) => {
-                const s = Math.floor(segmentCount / 2)
-                const step = 1 / segmentCount
-
-                // automatically adjust length so links/bonds that are rendered as two half cylinders
-                // have evenly spaced dashed cylinders
-                if (lengthScale < 1) {
-                    const bias = lengthScale / 2 / segmentCount
-                    lengthScale += segmentCount % 2 === 1 ? bias : -bias
-                }
-
-                const d = Vec3.distance(start, end) * lengthScale
-                props.height = d * step
-                const cylinder = getCylinder(props)
-                Vec3.sub(tmpCylinderDir, end, start)
-
-                for (let j = 0; j < s; ++j) {
-                    const f = step * (j * 2 + 1)
-                    Vec3.setMagnitude(tmpCylinderDir, tmpCylinderDir, d * f)
-                    Vec3.add(tmpCylinderStart, start, tmpCylinderDir)
-                    setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d * step)
-                    addPrimitive(tmpCylinderMat, cylinder)
-                }
-            },
-            addSphere: (center: Vec3, radius: number, detail: number) => {
-                addPrimitive(setSphereMat(tmpSphereMat, center, radius), getSphere(detail))
-            },
-
-            addTube: (centers: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, 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(groups, currentGroup);
-            },
-            addSheet: (controls: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, 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(groups, currentGroup);
-            },
-
-            setGroup: (group: number) => {
-                currentGroup = group
-            },
+            setGroup: (group: number) => { currentGroup = group },
             getMesh: () => {
                 const vb = ChunkedArray.compact(vertices, true) as Float32Array
                 const ib = ChunkedArray.compact(indices, true) as Uint32Array

+ 10 - 10
src/mol-geo/primitive/pyramid.ts

@@ -12,9 +12,9 @@ const on = Vec3.create(0, 0, -0.5), op = Vec3.create(0, 0, 0.5)
 const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero()
 
 /**
- * Create a pyramide with a poligonal base
+ * Create a pyramid with a poligonal base
  */
-export function Pyramide(points: ArrayLike<number>): Primitive {
+export function Pyramid(points: ArrayLike<number>): Primitive {
     const sideCount = points.length / 2
     const baseCount = sideCount === 3 ? 1 : sideCount === 4 ? 2 : sideCount
     const count = 2 * baseCount + 2 * sideCount
@@ -53,17 +53,17 @@ export function Pyramide(points: ArrayLike<number>): Primitive {
     return builder.getPrimitive()
 }
 
-let octagonalPyramide: Primitive
-export function OctagonalPyramide() {
-    if (!octagonalPyramide) octagonalPyramide = Pyramide(polygon(8, true))
-    return octagonalPyramide
+let octagonalPyramid: Primitive
+export function OctagonalPyramid() {
+    if (!octagonalPyramid) octagonalPyramid = Pyramid(polygon(8, true))
+    return octagonalPyramid
 }
 
 //
 
-let perforatedOctagonalPyramide: Primitive
+let perforatedOctagonalPyramid: Primitive
 export function PerforatedOctagonalPyramid() {
-    if (!perforatedOctagonalPyramide) {
+    if (!perforatedOctagonalPyramid) {
         const points = polygon(8, true)
         const vertices = new Float32Array(8 * 3 + 6)
         for (let i = 0; i < 8; ++i) {
@@ -81,7 +81,7 @@ export function PerforatedOctagonalPyramid() {
             0, 1, 8,  1, 2, 8,  4, 5, 8,  5, 6, 8,
             2, 3, 9,  3, 4, 9,  6, 7, 9,  7, 0, 9
         ];
-        perforatedOctagonalPyramide = createPrimitive(vertices, indices)
+        perforatedOctagonalPyramid = createPrimitive(vertices, indices)
     }
-    return perforatedOctagonalPyramide
+    return perforatedOctagonalPyramid
 }

+ 33 - 15
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -17,6 +17,12 @@ import { LocationIterator } from '../../../util/location-iterator';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { ComplexMeshVisual, DefaultComplexMeshProps } from '../complex-visual';
 import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+import { addSphere } from '../../../mesh/builder/sphere';
+import { Box, PerforatedBox } from '../../../primitive/box';
+import { OctagonalPyramid, PerforatedOctagonalPyramid } from '../../../primitive/pyramid';
+import { Star } from '../../../primitive/star';
+import { Octahedron, PerforatedOctahedron } from '../../../primitive/octahedron';
+import { DiamondPrism, PentagonalPrism, HexagonalPrism } from '../../../primitive/prism';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -25,6 +31,17 @@ const pd = Vec3.zero()
 const sideFactor = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
 const radiusFactor = 1.75
 
+const box = Box()
+const perforatedBox = PerforatedBox()
+const octagonalPyramid = OctagonalPyramid()
+const perforatedOctagonalPyramid = PerforatedOctagonalPyramid()
+const star = Star({ outerRadius: 1, innerRadius: 0.5, thickness: 0.5, pointCount: 5 })
+const octahedron = Octahedron()
+const perforatedOctahedron = PerforatedOctahedron()
+const diamondPrism = DiamondPrism()
+const pentagonalPrism = PentagonalPrism()
+const hexagonalPrism = HexagonalPrism()
+
 async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, props: CarbohydrateSymbolProps, mesh?: Mesh) {
     const builder = MeshBuilder.create(256, 128, mesh)
 
@@ -53,67 +70,68 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
 
         switch (shapeType) {
             case SaccharideShapes.FilledSphere:
-                builder.addSphere(center, radius, detail)
+                addSphere(builder, center, radius, detail)
                 break;
             case SaccharideShapes.FilledCube:
                 Mat4.scaleUniformly(t, t, side)
-                builder.addBox(t)
+                builder.add(t, box)
                 break;
             case SaccharideShapes.CrossedCube:
                 Mat4.scaleUniformly(t, t, side)
-                builder.addPerforatedBox(t)
+                builder.add(t, perforatedBox)
                 Mat4.mul(t, t, Mat4.rotZ90X180)
                 builder.setGroup(i * 2 + 1)
-                builder.addPerforatedBox(t)
+                builder.add(t, perforatedBox)
                 break;
             case SaccharideShapes.FilledCone:
                 Mat4.scaleUniformly(t, t, side * 1.2)
-                builder.addOctagonalPyramid(t)
+                builder.add(t, octagonalPyramid)
                 break
             case SaccharideShapes.DevidedCone:
                 Mat4.scaleUniformly(t, t, side * 1.2)
-                builder.addPerforatedOctagonalPyramid(t)
+                builder.add(t, perforatedOctagonalPyramid)
                 Mat4.mul(t, t, Mat4.rotZ90)
                 builder.setGroup(i * 2 + 1)
-                builder.addPerforatedOctagonalPyramid(t)
+                builder.add(t, perforatedOctagonalPyramid)
                 break
             case SaccharideShapes.FlatBox:
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
-                builder.addBox(t)
+                builder.add(t, box)
                 break
             case SaccharideShapes.FilledStar:
+                Mat4.scaleUniformly(t, t, side)
                 Mat4.mul(t, t, Mat4.rotZY90)
-                builder.addStar(t, { outerRadius: side, innerRadius: side / 2, thickness: side / 2, pointCount: 5 })
+                builder.add(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.addOctahedron(t)
+                builder.add(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.addPerforatedOctahedron(t)
+                builder.add(t, perforatedOctahedron)
                 Mat4.mul(t, t, Mat4.rotY90)
                 builder.setGroup(i * 2 + 1)
-                builder.addPerforatedOctahedron(t)
+                builder.add(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.addDiamondPrism(t)
+                builder.add(t, diamondPrism)
                 break
             case SaccharideShapes.Pentagon:
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
-                builder.addPentagonalPrism(t)
+                builder.add(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.addHexagonalPrism(t)
+                builder.add(t, hexagonalPrism)
                 break
         }
     }

+ 5 - 2
src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts

@@ -15,6 +15,8 @@ import { Segmentation, SortedArray } from 'mol-data/int';
 import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types';
 import { getElementIndexForAtomId, getElementIndexForAtomRole } from 'mol-model/structure/util';
 import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual';
+import { addCylinder } from '../../../mesh/builder/cylinder';
+import { Box } from '../../../primitive/box';
 
 const p1 = Vec3.zero()
 const p2 = Vec3.zero()
@@ -28,6 +30,7 @@ const vC = Vec3.zero()
 const center = Vec3.zero()
 const t = Mat4.identity()
 const sVec = Vec3.zero()
+const box = Box()
 
 // TODO define props, should be scalable
 async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
@@ -89,8 +92,8 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, props:
                     Mat4.scale(t, t, Vec3.set(sVec, width, depth, height))
                     Mat4.setTranslation(t, center)
                     builder.setGroup(SortedArray.findPredecessorIndex(elements, idx6))
-                    builder.addBox(t)
-                    builder.addCylinder(p5, p6, 1, { radiusTop: 0.2, radiusBottom: 0.2 })
+                    builder.add(t, box)
+                    addCylinder(builder, p5, p6, 1, { radiusTop: 0.2, radiusBottom: 0.2 })
                 }
             }
 

+ 3 - 2
src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts

@@ -16,6 +16,7 @@ import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual';
 import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
 import { CylinderProps } from '../../../primitive/cylinder';
 import { OrderedSet } from 'mol-data/int';
+import { addCylinder } from '../../../mesh/builder/cylinder';
 
 export interface PolymerBackboneCylinderProps {
     sizeTheme: SizeThemeProps
@@ -47,11 +48,11 @@ async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit
 
         cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerA)
         builder.setGroup(OrderedSet.findPredecessorIndex(elements, centerA.element))
-        builder.addCylinder(pA, pB, 0.5, cylinderProps)
+        addCylinder(builder, pA, pB, 0.5, cylinderProps)
 
         cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerB)
         builder.setGroup(OrderedSet.findPredecessorIndex(elements, centerB.element))
-        builder.addCylinder(pB, pA, 0.5, cylinderProps)
+        addCylinder(builder, pB, pA, 0.5, cylinderProps)
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {
             await ctx.update({ message: 'Backbone mesh', current: i, max: polymerElementCount });

+ 4 - 1
src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts

@@ -16,6 +16,7 @@ import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/
 import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual';
 import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
 import { OrderedSet } from 'mol-data/int';
+import { Wedge } from '../../../primitive/wedge';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -27,6 +28,8 @@ const depthFactor = 4
 const widthFactor = 4
 const heightFactor = 6
 
+const wedge = Wedge()
+
 export interface PolymerDirectionWedgeProps {
     sizeTheme: SizeThemeProps
 }
@@ -71,7 +74,7 @@ async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit,
             Mat4.mul(t, t, Mat4.rotY90Z180)
             Mat4.scale(t, t, Vec3.set(sVec, height, width, depth))
             Mat4.setTranslation(t, v.p2)
-            builder.addWedge(t)
+            builder.add(t, wedge)
         }
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {

+ 5 - 3
src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts

@@ -15,6 +15,8 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
 import { CylinderProps } from '../../../primitive/cylinder';
+import { addSphere } from '../../../mesh/builder/sphere';
+import { addFixedCountDashedCylinder } from '../../../mesh/builder/cylinder';
 
 const segmentCount = 10
 
@@ -49,7 +51,7 @@ async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, pro
         if (centerA.element === centerB.element) {
             builder.setGroup(centerA.element)
             pos(elements[centerA.element], pA)
-            builder.addSphere(pA, 0.6, 0)
+            addSphere(builder, pA, 0.6, 0)
         } else {
             const elmA = elements[centerA.element]
             const elmB = elements[centerB.element]
@@ -59,12 +61,12 @@ async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, pro
             l.element = elmA
             cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l)
             builder.setGroup(centerA.element)
-            builder.addFixedCountDashedCylinder(pA, pB, 0.5, segmentCount, cylinderProps)
+            addFixedCountDashedCylinder(builder, pA, pB, 0.5, segmentCount, cylinderProps)
 
             l.element = elmB
             cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l)
             builder.setGroup(centerB.element)
-            builder.addFixedCountDashedCylinder(pB, pA, 0.5, segmentCount, cylinderProps)
+            addFixedCountDashedCylinder(builder, pB, pA, 0.5, segmentCount, cylinderProps)
         }
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {

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

@@ -15,6 +15,8 @@ import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/
 import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
 import { OrderedSet } from 'mol-data/int';
+import { addSheet } from '../../../mesh/builder/sheet';
+import { addTube } from '../../../mesh/builder/tube';
 
 export interface PolymerTraceMeshProps {
     sizeTheme: SizeThemeProps
@@ -57,7 +59,7 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: Po
         if (isSheet) {
             const height = width * aspectRatio
             const arrowHeight = v.secStrucChange ? height * arrowFactor : 0
-            builder.addSheet(curvePoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, true, true)
+            addSheet(builder, curvePoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, true, true)
         } else {
             let height: number
             if (isHelix) {
@@ -68,7 +70,7 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: Po
             } else {
                 height = width
             }
-            builder.addTube(curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, true, true)
+            addTube(builder, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, true, true)
         }
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {

+ 2 - 1
src/mol-geo/representation/structure/visual/util/element.ts

@@ -15,6 +15,7 @@ import { Interval, OrderedSet } from 'mol-data/int';
 import { PickingId } from '../../../../util/picking';
 import { SizeTheme, SizeThemeProps } from 'mol-view/theme/size';
 import { LocationIterator } from '../../../../util/location-iterator';
+import { addSphere } from '../../../../mesh/builder/sphere';
 
 export interface ElementSphereMeshProps {
     sizeTheme: SizeThemeProps,
@@ -40,7 +41,7 @@ export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, p
         pos(elements[i], v)
 
         meshBuilder.setGroup(i)
-        meshBuilder.addSphere(v, sizeTheme.size(l), detail)
+        addSphere(meshBuilder, v, sizeTheme.size(l), detail)
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {
             await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount });

+ 5 - 4
src/mol-geo/representation/structure/visual/util/link.ts

@@ -14,6 +14,7 @@ import { SizeThemeProps } from 'mol-view/theme/size';
 import { CylinderProps } from '../../../../primitive/cylinder';
 import { LocationIterator } from '../../../../util/location-iterator';
 import { Unit, StructureElement, Structure, Link } from 'mol-model/structure';
+import { addFixedCountDashedCylinder, addCylinder, addDoubleCylinder } from '../../../../mesh/builder/cylinder';
 
 export const DefaultLinkCylinderProps = {
     ...DefaultMeshProps,
@@ -94,7 +95,7 @@ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: L
         if (LinkType.is(f, LinkType.Flag.MetallicCoordination)) {
             // show metall coordinations with dashed cylinders
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius / 3
-            meshBuilder.addFixedCountDashedCylinder(va, vb, 0.5, 7, cylinderProps)
+            addFixedCountDashedCylinder(meshBuilder, 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))
@@ -105,11 +106,11 @@ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: L
 
             cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius
 
-            if (o === 3) meshBuilder.addCylinder(va, vb, 0.5, cylinderProps)
-            meshBuilder.addDoubleCylinder(va, vb, 0.5, vShift, cylinderProps)
+            if (o === 3) addCylinder(meshBuilder, va, vb, 0.5, cylinderProps)
+            addDoubleCylinder(meshBuilder, va, vb, 0.5, vShift, cylinderProps)
         } else {
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius
-            meshBuilder.addCylinder(va, vb, 0.5, cylinderProps)
+            addCylinder(meshBuilder, va, vb, 0.5, cylinderProps)
         }
 
         if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) {

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

@@ -107,9 +107,9 @@ export class Stage {
         // this.loadPdbid('1sfi') // contains cyclic peptid
         // this.loadPdbid('3sn6') // discontinuous chains
         // this.loadPdbid('2zex') // contains carbohydrate polymer
-        this.loadPdbid('3sgj') // contains carbohydrate polymer
+        // this.loadPdbid('3sgj') // contains carbohydrate polymer
         // this.loadPdbid('3ina') // contains GlcN and IdoA
-        // this.loadPdbid('1umz') // contains Xyl (Xyloglucan)
+        this.loadPdbid('1umz') // contains Xyl (Xyloglucan)
         // this.loadPdbid('1mfb') // contains Abe
         // this.loadPdbid('2gdu') // contains sucrose
         // this.loadPdbid('2fnc') // contains maltotriose