Browse Source

wip, carbohydrate visual, primitive geo refactoring

Alexander Rose 6 years ago
parent
commit
ffb7824b84

+ 23 - 0
src/mol-geo/primitive/polygon.ts

@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+/**
+ * Create points for a polygon:
+ * 3 for a triangle, 4 for a rectangle, 5 for a pentagon, 6 for a hexagon...
+ */
+export function polygon(sideCount: number, shift: boolean) {
+    const points = new Float32Array(sideCount * 2)
+    const radius = sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6
+
+    const offset = shift ? 0 : 1
+
+    for (let i = 0, il = 2 * sideCount; i < il; i += 2) {
+        const c = (i + offset) / sideCount * Math.PI
+        points[i] = Math.cos(c) * radius
+        points[i + 1] = Math.sin(c) * radius
+    }
+    return points
+}

+ 0 - 1
src/mol-geo/primitive/polyhedron.ts

@@ -29,7 +29,6 @@ export function Polyhedron(_vertices: ArrayLike<number>, _indices: ArrayLike<num
 
     const normals = new Float32Array(vertices.length);
     computeIndexedVertexNormals(vertices, indices, normals)
-    // this.normalizeNormals(); // smooth normals
 
     return {
         vertices: new Float32Array(vertices),

+ 37 - 18
src/mol-geo/primitive/primitive.ts

@@ -12,29 +12,48 @@ export interface Primitive {
     indices: ArrayLike<number>
 }
 
-const tri = [ Vec3.zero(), Vec3.zero(), Vec3.zero() ]
-const n = Vec3.zero()
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero()
 
 /** Create primitive with face normals from vertices and indices */
-export function createPrimitive(_vertices: ArrayLike<number>, _indices: ArrayLike<number>): Primitive {
-    const count = _indices.length
-    const vertices = new Float32Array(count * 3)
-    const normals = new Float32Array(count * 3)
-    const indices = new Uint32Array(count)
+export function createPrimitive(vertices: ArrayLike<number>, indices: ArrayLike<number>): Primitive {
+    const count = indices.length
+    const builder = PrimitiveBuilder(count / 3)
 
     for (let i = 0; i < count; i += 3) {
-        for (let j = 0; j < 3; ++j) {
-            Vec3.fromArray(tri[j], _vertices, _indices[i + j] * 3)
-            Vec3.toArray(tri[j], vertices, i * 3 + j * 3)
-        }
+        Vec3.fromArray(a, vertices, indices[i] * 3)
+        Vec3.fromArray(b, vertices, indices[i + 1] * 3)
+        Vec3.fromArray(c, vertices, indices[i + 2] * 3)
+        builder.add(a, b, c)
+    }
+    return builder.getPrimitive()
+}
 
-        Vec3.triangleNormal(n, tri[0], tri[1], tri[2])
+export interface PrimitiveBuilder {
+    add(a: Vec3, b: Vec3, c: Vec3): void
+    getPrimitive(): Primitive
+}
 
-        for (let j = 0; j < 3; ++j) {
-            Vec3.toArray(n, normals, i * 3 + j * 3)
-            indices[i + j] = i + j
-        }
+const vn = Vec3.zero()
+
+/** Builder to create primitive with face normals */
+export function PrimitiveBuilder(triangleCount: number): PrimitiveBuilder {
+    const vertices = new Float32Array(triangleCount * 3 * 3)
+    const normals = new Float32Array(triangleCount * 3 * 3)
+    const indices = new Uint32Array(triangleCount * 3)
+    let offset = 0
+
+    return {
+        add: (a: Vec3, b: Vec3, c: Vec3) => {
+            Vec3.toArray(a, vertices, offset)
+            Vec3.toArray(b, vertices, offset + 3)
+            Vec3.toArray(c, vertices, offset + 6)
+            Vec3.triangleNormal(vn, a, b, c)
+            for (let j = 0; j < 3; ++j) {
+                Vec3.toArray(vn, normals, offset + 3 * j)
+                indices[offset / 3 + j] = offset / 3 + j
+            }
+            offset += 9
+        },
+        getPrimitive: () => ({ vertices, normals, indices })
     }
-
-    return { vertices, normals, indices }
 }

+ 101 - 0
src/mol-geo/primitive/prism.ts

@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra'
+import { Primitive, PrimitiveBuilder } from './primitive';
+import { polygon } from './polygon'
+
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero()
+
+/**
+ * Create a prism with a poligonal base
+ */
+export function Prism(points: ArrayLike<number>): Primitive {
+    const sideCount = points.length / 2
+    const baseCount = sideCount === 3 ? 1 : sideCount === 4 ? 2 : sideCount
+    const count = 2 * baseCount + 2 * sideCount
+    const builder = PrimitiveBuilder(count)
+
+    // create sides
+    for (let i = 0; i < sideCount; ++i) {
+        const ni = (i + 1) % sideCount
+        Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5)
+        Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5)
+        Vec3.set(c, points[ni * 2], points[ni * 2 + 1], 0.5)
+        Vec3.set(d, points[i * 2], points[i * 2 + 1], 0.5)
+        builder.add(a, b, c)
+        builder.add(c, d, a)
+    }
+
+    // create bases
+    if (sideCount === 3) {
+        Vec3.set(a, points[0], points[1], -0.5)
+        Vec3.set(b, points[2], points[3], -0.5)
+        Vec3.set(c, points[4], points[5], -0.5)
+        builder.add(a, b, c)
+        Vec3.set(a, points[0], points[1], 0.5)
+        Vec3.set(b, points[2], points[3], 0.5)
+        Vec3.set(c, points[4], points[5], 0.5)
+        builder.add(c, b, a)
+    } else if (sideCount === 4) {
+        Vec3.set(a, points[0], points[1], -0.5)
+        Vec3.set(b, points[2], points[3], -0.5)
+        Vec3.set(c, points[4], points[5], -0.5)
+        Vec3.set(d, points[6], points[7], -0.5)
+        builder.add(a, b, c)
+        builder.add(c, d, a)
+        Vec3.set(a, points[0], points[1], 0.5)
+        Vec3.set(b, points[2], points[3], 0.5)
+        Vec3.set(c, points[4], points[5], 0.5)
+        Vec3.set(d, points[6], points[7], 0.5)
+        builder.add(a, b, c)
+        builder.add(c, d, a)
+    } else {
+        for (let i = 0; i < sideCount; ++i) {
+            const ni = (i + 1) % sideCount
+            Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5)
+            Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5)
+            Vec3.set(c, 0, 0, -0.5)
+            builder.add(a, b, c)
+            Vec3.set(a, points[i * 2], points[i * 2 + 1], 0.5)
+            Vec3.set(b, points[ni * 2], points[ni * 2 + 1], 0.5)
+            Vec3.set(c, 0, 0, 0.5)
+            builder.add(c, b, a)
+        }
+    }
+
+    return builder.getPrimitive()
+}
+
+let wedge: Primitive
+export function Wedge() {
+    if (!wedge) wedge = Prism(polygon(3, false))
+    return wedge
+}
+
+let box: Primitive
+export function Box() {
+    if (!box) box = Prism(polygon(4, false))
+    return box
+}
+
+let diamond: Primitive
+export function DiamondPrism() {
+    if (!diamond) diamond = Prism(polygon(4, false))
+    return diamond
+}
+
+let hexagonalPrism: Primitive
+export function HexagonalPrism() {
+    if (!hexagonalPrism) hexagonalPrism = Prism(polygon(5, false))
+    return hexagonalPrism
+}
+
+let pentagonalPrism: Primitive
+export function PentagonalPrism() {
+    if (!pentagonalPrism) pentagonalPrism = Prism(polygon(6, true))
+    return pentagonalPrism
+}

+ 14 - 34
src/mol-geo/primitive/star.ts

@@ -5,7 +5,7 @@
  */
 
 import { Vec3 } from 'mol-math/linear-algebra'
-import { Primitive } from './primitive';
+import { Primitive, PrimitiveBuilder } from './primitive';
 
 export const DefaultStarProps = {
     pointCount: 5,
@@ -15,29 +15,21 @@ export const DefaultStarProps = {
 }
 export type StarProps = Partial<typeof DefaultStarProps>
 
-const op = Vec3.zero()
-const on = Vec3.zero()
-const vn = Vec3.zero()
-const p1 = Vec3.zero()
-const p2 = Vec3.zero()
-const p3 = Vec3.zero()
+const op = Vec3.zero(), on = Vec3.zero()
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero()
 
 export function Star(props?: StarProps): Primitive {
     const { outerRadius, innerRadius, thickness, pointCount } = { ...DefaultStarProps, ...props }
 
     const triangleCount = pointCount * 2 * 2
-    const vertexCount = triangleCount * 3
-
-    const vertices = new Float32Array(vertexCount * 3)
-    const normals = new Float32Array(vertexCount * 3)
-    const indices = new Uint32Array(triangleCount * 3)
+    const builder = PrimitiveBuilder(triangleCount)
 
     const innerPoints = new Float32Array(pointCount * 2)
     const outerPoints = new Float32Array(pointCount * 2)
 
     for (let i = 0; i < pointCount; ++i) {
         const io = i * 2, ii = i * 2 + 1
-        const co = io / pointCount * Math.PI, ci = ii / pointCount * Math.PI
+        const co = (io + 1) / pointCount * Math.PI, ci = (ii + 1) / pointCount * Math.PI
         outerPoints[io] = Math.cos(co) * outerRadius
         outerPoints[ii] = Math.sin(co) * outerRadius
         innerPoints[io] = Math.cos(ci) * innerRadius
@@ -47,29 +39,17 @@ export function Star(props?: StarProps): Primitive {
     Vec3.set(op, 0, 0, thickness / 2)
     Vec3.set(on, 0, 0, -thickness / 2)
 
-    function add(a: Vec3, b: Vec3, c: Vec3, offset: number) {
-        Vec3.toArray(a, vertices, offset)
-        Vec3.toArray(b, vertices, offset + 3)
-        Vec3.toArray(c, vertices, offset + 6)
-        Vec3.triangleNormal(vn, a, b, c)
-        for (let j = 0; j < 3; ++j) {
-            Vec3.toArray(vn, normals, offset + 3 * j)
-            indices[offset / 3 + j] = offset / 3 + j
-        }
-    }
-
     for (let i = 0; i < pointCount; ++i) {
         const ni = (i + 1) % pointCount
-        Vec3.set(p1, outerPoints[i * 2], outerPoints[i * 2 + 1], 0)
-        Vec3.set(p2, innerPoints[i * 2], innerPoints[i * 2 + 1], 0)
-        Vec3.set(p3, outerPoints[ni * 2], outerPoints[ni * 2 + 1], 0)
-
-        const offset = i * 3 * 3 * 4
-        add(op, p1, p2, offset)
-        add(on, p1, p2, offset + 9)
-        add(op, p2, p3, offset + 18)
-        add(on, p2, p3, offset + 27)
+        Vec3.set(a, outerPoints[i * 2], outerPoints[i * 2 + 1], 0)
+        Vec3.set(b, innerPoints[i * 2], innerPoints[i * 2 + 1], 0)
+        Vec3.set(c, outerPoints[ni * 2], outerPoints[ni * 2 + 1], 0)
+
+        builder.add(op, a, b)
+        builder.add(on, a, b)
+        builder.add(op, b, c)
+        builder.add(on, b, c)
     }
 
-    return { vertices, normals, indices }
+    return builder.getPrimitive()
 }

+ 36 - 23
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -24,16 +24,16 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { createUniformColor } from '../../../util/color-data';
 import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants';
 
+const t = Mat4.identity()
+const sVec = Vec3.zero()
+const p = Vec3.zero()
+const pd = Vec3.zero()
+const p1 = Vec3.zero()
+const p2 = Vec3.zero()
+
 async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, mesh?: Mesh) {
     const builder = MeshBuilder.create(256, 128, mesh)
 
-    const t = Mat4.identity()
-    const sMat = Mat4.identity()
-    const sVec = Vec3.zero()
-    const p = Vec3.zero()
-    const pd = Vec3.zero()
-    const p1 = Vec3.zero()
-    const p2 = Vec3.zero()
     const carbohydrates = structure.carbohydrates
 
     function centerAlign(center: Vec3, normal: Vec3, direction: Vec3) {
@@ -44,7 +44,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
 
     const side = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
     const radius = 1.75
-    const coneParams = { radiusTop: 0.0, radiusBottom: radius, bottomCap: true }
+    const coneParams = { radiusTop: radius, radiusBottom: 0.0, topCap: true }
 
     const linkParams = { radiusTop: 0.4, radiusBottom: 0.4 }
 
@@ -54,61 +54,74 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
 
         const cGeo = c.geometry!
         const shapeType = getSaccharideShape(c.component.type)
+
         switch (shapeType) {
             case SaccharideShapes.FilledSphere:
                 builder.addSphere(cGeo.center, radius, 2)
                 break;
             case SaccharideShapes.FilledCube:
                 centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
-                builder.addBox(t, { width: side, height: side, depth: side })
-                builder.addOctahedron(t)
+                Mat4.scaleUniformly(t, t, side)
+                builder.addBox(t)
                 break;
             case SaccharideShapes.CrossedCube:
                 // TODO split
                 centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
-                builder.addBox(t, { width: side, height: side, depth: side })
+                Mat4.scaleUniformly(t, t, side)
+                builder.addBox(t)
                 break;
             case SaccharideShapes.FilledCone:
-                Vec3.scaleAndAdd(p1, cGeo.center, cGeo.direction, radius)
-                Vec3.scaleAndSub(p2, cGeo.center, cGeo.direction, radius)
+                Vec3.scaleAndSub(p1, cGeo.center, cGeo.direction, radius)
+                Vec3.scaleAndAdd(p2, cGeo.center, cGeo.direction, radius)
                 builder.addCylinder(p1, p2, 1, coneParams)
                 break
             case SaccharideShapes.DevidedCone:
                 // TODO split
-                Vec3.scaleAndAdd(p1, cGeo.center, cGeo.direction, radius)
-                Vec3.scaleAndSub(p2, cGeo.center, cGeo.direction, radius)
+                Vec3.scaleAndSub(p1, cGeo.center, cGeo.direction, radius)
+                Vec3.scaleAndAdd(p2, cGeo.center, cGeo.direction, radius)
                 builder.addCylinder(p1, p2, 1, coneParams)
                 break
             case SaccharideShapes.FlatBox:
                 centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
-                builder.addBox(t, { width: side, height: side / 2, depth: side })
+                Mat4.mul(t, t, Mat4.rotY90)
+                Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
+                builder.addBox(t)
                 break
             case SaccharideShapes.FilledStar:
-                centerAlign(cGeo.center, cGeo.direction, cGeo.normal)
+                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
+                Mat4.mul(t, t, Mat4.rotY90)
                 builder.addStar(t, { outerRadius: side, innerRadius: side / 2, thickness: side / 2, pointCount: 5 })
                 break
             case SaccharideShapes.FilledDiamond:
                 centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
-                Mat4.fromScaling(sMat, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
-                Mat4.mul(t, t, sMat)
+                Mat4.mul(t, t, Mat4.rotY90)
+                Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
                 builder.addOctahedron(t)
                 break
             case SaccharideShapes.DividedDiamond:
                 // TODO split
                 centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
-                Mat4.fromScaling(sMat, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
-                Mat4.mul(t, t, sMat)
+                Mat4.mul(t, t, Mat4.rotY90)
+                Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
                 builder.addOctahedron(t)
                 break
             case SaccharideShapes.FlatDiamond:
+                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
+                Mat4.mul(t, t, Mat4.rotY90)
+                Mat4.scale(t, t, Vec3.set(sVec, side, side / 2, side / 2))
+                builder.addDiamondPrism(t)
             case SaccharideShapes.Pentagon:
                 centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
-                builder.addBox(t, { width: side, height: side, depth: 0.5 })
+                Mat4.mul(t, t, Mat4.rotY90)
+                Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
+                builder.addPentagonalPrism(t)
                 break
             case SaccharideShapes.FlatHexagon:
             default:
                 centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
-                builder.addBox(t, { width: side, height: side, depth: 0.1 })
+                Mat4.mul(t, t, Mat4.rotY90)
+                Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
+                builder.addHexagonalPrism(t)
                 break
         }
     }

+ 15 - 13
src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts

@@ -27,6 +27,19 @@ 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';
 
+const p1 = Vec3.zero()
+const p2 = Vec3.zero()
+const p3 = Vec3.zero()
+const p4 = Vec3.zero()
+const p5 = Vec3.zero()
+const p6 = Vec3.zero()
+const v12 = Vec3.zero()
+const v34 = Vec3.zero()
+const vC = Vec3.zero()
+const center = Vec3.zero()
+const t = Mat4.identity()
+const sVec = Vec3.zero()
+
 async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
@@ -41,18 +54,6 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?:
     const chainIt = Segmentation.transientSegments(chainAtomSegments, elements)
     const residueIt = Segmentation.transientSegments(residueAtomSegments, elements)
 
-    const p1 = Vec3.zero()
-    const p2 = Vec3.zero()
-    const p3 = Vec3.zero()
-    const p4 = Vec3.zero()
-    const p5 = Vec3.zero()
-    const p6 = Vec3.zero()
-    const v12 = Vec3.zero()
-    const v34 = Vec3.zero()
-    const vC = Vec3.zero()
-    const center = Vec3.zero()
-    const t = Mat4.identity()
-
     let i = 0
     while (chainIt.hasNext) {
         residueIt.setSegment(chainIt.move());
@@ -94,9 +95,10 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?:
                     Vec3.normalize(vC, Vec3.cross(vC, v12, v34))
                     Mat4.targetTo(t, p1, p2, vC)
                     Vec3.scaleAndAdd(center, p1, v12, height / 2 - 0.2)
+                    Mat4.scale(t, t, Vec3.set(sVec, width, depth, height))
                     Mat4.setTranslation(t, center)
                     builder.setId(SortedArray.findPredecessorIndex(elements, idx6))
-                    builder.addBox(t, { width: width, height: depth, depth: height })
+                    builder.addBox(t)
                     builder.addCylinder(p5, p6, 1, { radiusTop: 0.2, radiusBottom: 0.2 })
                 }
             }

+ 14 - 6
src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts

@@ -28,6 +28,10 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
 
 const t = Mat4.identity()
+const sVec = Vec3.zero()
+const n0 = Vec3.zero()
+const n1 = Vec3.zero()
+const upVec = Vec3.zero()
 
 async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
@@ -51,23 +55,27 @@ async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit,
         const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)
         const tension = (isNucleic || isSheet) ? 0.5 : 0.9
 
-        // console.log('ELEMENT', i)
         interpolateCurveSegment(state, v, tension)
 
         if ((isSheet && !v.secStrucChange) || !isSheet) {
-            const upVec = Vec3.zero()
+
             let width = 0.5, height = 1.2, depth = 0.6
             if (isNucleic) {
-                Vec3.fromArray(upVec, binormalVectors, Math.round(linearSegments / 2) * 3)
+                Vec3.fromArray(n0, binormalVectors, 0)
+                Vec3.fromArray(n1, binormalVectors, 3)
+                Vec3.normalize(upVec, Vec3.add(upVec, n0, n1))
                 depth = 0.9
             } else {
-                Vec3.fromArray(upVec, normalVectors, Math.round(linearSegments / 2) * 3)
+                Vec3.fromArray(n0, normalVectors, 0)
+                Vec3.fromArray(n1, normalVectors, 3)
+                Vec3.normalize(upVec, Vec3.add(upVec, n0, n1))
             }
 
             Mat4.targetTo(t, v.p3, v.p1, upVec)
-            Mat4.mul(t, t, Mat4.rotXY90)
+            Mat4.mul(t, t, Mat4.rotY90)
+            Mat4.scale(t, t, Vec3.set(sVec, height, width, depth))
             Mat4.setTranslation(t, v.p2)
-            builder.addWedge(t, { width, height, depth })
+            builder.addWedge(t)
         }
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {

+ 22 - 8
src/mol-geo/shape/mesh-builder.ts

@@ -8,9 +8,7 @@ import { ValueCell } from 'mol-util/value-cell'
 import { Vec3, Mat4, Mat3 } from 'mol-math/linear-algebra';
 import { ChunkedArray } from 'mol-data/util';
 
-import { Box, BoxProps } from '../primitive/box';
 import { Plane, PlaneProps } from '../primitive/plane';
-import { Wedge, WedgeProps } from '../primitive/wedge';
 import { Cylinder, CylinderProps } from '../primitive/cylinder';
 import { Sphere, SphereProps } from '../primitive/sphere';
 import { Mesh } from './mesh';
@@ -20,6 +18,7 @@ import { addTube } from '../primitive/tube';
 import { StarProps, Star } from '../primitive/star';
 import { Octahedron } from '../primitive/octahedron';
 import { Primitive } from '../primitive/primitive';
+import { Wedge, Box, DiamondPrism, PentagonalPrism, HexagonalPrism } from '../primitive/prism';
 
 export interface MeshBuilderState {
     vertices: ChunkedArray<number, 3>
@@ -29,9 +28,12 @@ export interface MeshBuilderState {
 
 export interface MeshBuilder {
     add(t: Mat4, _vertices: ArrayLike<number>, _normals: ArrayLike<number>, _indices?: ArrayLike<number>): void
-    addBox(t: Mat4, props?: BoxProps): void
+    addBox(t: Mat4): void
     addPlane(t: Mat4, props?: PlaneProps): void
-    addWedge(t: Mat4, props?: WedgeProps): void
+    addWedge(t: Mat4): void
+    addDiamondPrism(t: Mat4): void
+    addPentagonalPrism(t: Mat4): void
+    addHexagonalPrism(t: Mat4): void
     addStar(t: Mat4, props?: StarProps): void
     addOctahedron(t: Mat4): void
     addCylinder(start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps): void
@@ -132,16 +134,28 @@ export namespace MeshBuilder {
 
         return {
             add,
-            addBox: (t: Mat4, props?: BoxProps) => {
-                const { vertices, normals, indices } = Box(props)
+            addBox: (t: Mat4) => {
+                const { vertices, normals, indices } = Box()
                 add(t, vertices, normals, indices)
             },
             addPlane: (t: Mat4, props?: PlaneProps) => {
                 const { vertices, normals, indices } = Plane(props)
                 add(t, vertices, normals, indices)
             },
-            addWedge: (t: Mat4, props?: WedgeProps) => {
-                const { vertices, normals, indices } = Wedge(props)
+            addWedge: (t: Mat4) => {
+                const { vertices, normals, indices } = Wedge()
+                add(t, vertices, normals, indices)
+            },
+            addDiamondPrism: (t: Mat4) => {
+                const { vertices, normals, indices } = DiamondPrism()
+                add(t, vertices, normals, indices)
+            },
+            addPentagonalPrism: (t: Mat4) => {
+                const { vertices, normals, indices } = PentagonalPrism()
+                add(t, vertices, normals, indices)
+            },
+            addHexagonalPrism: (t: Mat4) => {
+                const { vertices, normals, indices } = HexagonalPrism()
                 add(t, vertices, normals, indices)
             },
             addStar: (t: Mat4, props?: StarProps) => {

+ 40 - 0
src/mol-math/linear-algebra/3d/mat4.ts

@@ -489,6 +489,26 @@ namespace Mat4 {
         return out;
     }
 
+    export function scaleUniformly(out: Mat4, a: Mat4, scale: number) {
+        out[0] = a[0] * scale;
+        out[1] = a[1] * scale;
+        out[2] = a[2] * scale;
+        out[3] = a[3] * scale;
+        out[4] = a[4] * scale;
+        out[5] = a[5] * scale;
+        out[6] = a[6] * scale;
+        out[7] = a[7] * scale;
+        out[8] = a[8] * scale;
+        out[9] = a[9] * scale;
+        out[10] = a[10] * scale;
+        out[11] = a[11] * scale;
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+        return out;
+    }
+
     export function fromScaling(out: Mat4, v: Vec3) {
         out[0] = v[0];
         out[1] = 0;
@@ -509,6 +529,26 @@ namespace Mat4 {
         return out;
     }
 
+    export function fromUniformScaling(out: Mat4, scale: number) {
+        out[0] = scale;
+        out[1] = 0;
+        out[2] = 0;
+        out[3] = 0;
+        out[4] = 0;
+        out[5] = scale;
+        out[6] = 0;
+        out[7] = 0;
+        out[8] = 0;
+        out[9] = 0;
+        out[10] = scale;
+        out[11] = 0;
+        out[12] = 0;
+        out[13] = 0;
+        out[14] = 0;
+        out[15] = 1;
+        return out;
+    }
+
     export function makeTable(m: Mat4) {
         let ret = '';
         for (let i = 0; i < 4; i++) {

+ 4 - 2
src/mol-model/structure/structure/carbohydrates/compute.ts

@@ -206,7 +206,8 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                     const elementIndexB = elementsWithRingMap.get(elementKey(getResidueIndex(indexB, unitB), unitB.id))
 
                     if (elementIndexA !== undefined && elementIndexB !== undefined) {
-                        if (getAtomId(unitA, indexA).startsWith('C1')) {
+                        const atomIdA = getAtomId(unitA, indexA)
+                        if (atomIdA.startsWith('O1') || atomIdA.startsWith('C1')) {
                             fixLinkDirection(elementIndexA, elementIndexB)
                         }
                         links.push({
@@ -214,7 +215,8 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                             carbohydrateIndexB: elementIndexB
                         })
                     } else if (elementIndexA !== undefined) {
-                        if (getAtomId(unitA, indexA).startsWith('C1')) {
+                        const atomIdA = getAtomId(unitA, indexA)
+                        if (atomIdA.startsWith('O1') || atomIdA.startsWith('C1')) {
                             fixTerminalLinkDirection(elementIndexA, indexB, unitB)
                         }
                         terminalLinks.push({

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

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