Bladeren bron

wip, carbohydrate coloring and divided symbols

Alexander Rose 6 jaren geleden
bovenliggende
commit
21e010e847

+ 8 - 24
src/mol-app/ui/transform/backbone.tsx

@@ -15,21 +15,12 @@ import { Toggle } from '../controls/common';
 import { BackboneEntity } from 'mol-view/state/entity';
 import { BackboneUpdate } from 'mol-view/state/transform'
 import { StateContext } from 'mol-view/state/context';
-import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { ColorTheme, SizeTheme, ColorThemeNames, ColorThemeName } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { Unit } from 'mol-model/structure';
 
-export const ColorThemeInfo = {
-    'atom-index': {},
-    'chain-id': {},
-    'element-symbol': {},
-    'instance-index': {},
-    'uniform': {}
-}
-export type ColorThemeInfo = keyof typeof ColorThemeInfo
-
 interface BackboneState {
     doubleSided: boolean
     flipSided: boolean
@@ -86,7 +77,7 @@ export class Backbone extends View<Controller<any>, BackboneState, { transform:
             return <option key={value} value={value}>{value.toString()}</option>
         })
 
-        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+        const colorThemeOptions = ColorThemeNames.map((name, idx) => {
             return <option key={name} value={name}>{name}</option>
         })
 
@@ -137,19 +128,12 @@ export class Backbone extends View<Controller<any>, BackboneState, { transform:
                                     className='molstar-form-control'
                                     value={this.state.colorTheme.name}
                                     onChange={(e) => {
-                                        const colorThemeName = e.target.value as ColorThemeInfo
-                                        if (colorThemeName === 'uniform') {
-                                            this.update({
-                                                colorTheme: {
-                                                    name: colorThemeName,
-                                                    value: this.state.colorValue
-                                                }
-                                            })
-                                        } else {
-                                            this.update({
-                                                colorTheme: { name: colorThemeName }
-                                            })
-                                        }
+                                        this.update({
+                                            colorTheme: {
+                                                name: e.target.value as ColorThemeName,
+                                                value: this.state.colorValue
+                                            }
+                                        })
                                     }}
                                 >
                                     {colorThemeOptions}

+ 9 - 15
src/mol-app/ui/transform/ball-and-stick.tsx

@@ -15,7 +15,7 @@ import { Toggle } from '../controls/common';
 import { DistanceRestraintEntity } from 'mol-view/state/entity';
 import { DistanceRestraintUpdate } from 'mol-view/state/transform'
 import { StateContext } from 'mol-view/state/context';
-import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { ColorTheme, SizeTheme, ColorThemeName, ColorThemeNames } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
@@ -23,6 +23,7 @@ import { Unit } from 'mol-model/structure';
 
 export const ColorThemeInfo = {
     'atom-index': {},
+    'carbohydrate-symbol': {},
     'chain-id': {},
     'element-symbol': {},
     'instance-index': {},
@@ -89,7 +90,7 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra
             return <option key={name} value={name}>{name}</option>
         })
 
-        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+        const colorThemeOptions = ColorThemeNames.map((name, idx) => {
             return <option key={name} value={name}>{name}</option>
         })
 
@@ -128,19 +129,12 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra
                                     className='molstar-form-control'
                                     value={this.state.colorTheme.name}
                                     onChange={(e) => {
-                                        const colorThemeName = e.target.value as ColorThemeInfo
-                                        if (colorThemeName === 'uniform') {
-                                            this.update({
-                                                colorTheme: {
-                                                    name: colorThemeName,
-                                                    value: this.state.colorValue
-                                                }
-                                            })
-                                        } else {
-                                            this.update({
-                                                colorTheme: { name: colorThemeName }
-                                            })
-                                        }
+                                        this.update({
+                                            colorTheme: {
+                                                name: e.target.value as ColorThemeName,
+                                                value: this.state.colorValue
+                                            }
+                                        })
                                     }}
                                 >
                                     {colorThemeOptions}

+ 9 - 15
src/mol-app/ui/transform/cartoon.tsx

@@ -15,7 +15,7 @@ import { Toggle } from '../controls/common';
 import { CartoonEntity } from 'mol-view/state/entity';
 import { CartoonUpdate } from 'mol-view/state/transform'
 import { StateContext } from 'mol-view/state/context';
-import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { ColorTheme, SizeTheme, ColorThemeName, ColorThemeNames } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
@@ -23,6 +23,7 @@ import { Unit } from 'mol-model/structure';
 
 export const ColorThemeInfo = {
     'atom-index': {},
+    'carbohydrate-symbol': {},
     'chain-id': {},
     'element-symbol': {},
     'instance-index': {},
@@ -86,7 +87,7 @@ export class Cartoon extends View<Controller<any>, CartoonState, { transform: Ca
             return <option key={value} value={value}>{value.toString()}</option>
         })
 
-        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+        const colorThemeOptions = ColorThemeNames.map((name, idx) => {
             return <option key={name} value={name}>{name}</option>
         })
 
@@ -137,19 +138,12 @@ export class Cartoon extends View<Controller<any>, CartoonState, { transform: Ca
                                     className='molstar-form-control'
                                     value={this.state.colorTheme.name}
                                     onChange={(e) => {
-                                        const colorThemeName = e.target.value as ColorThemeInfo
-                                        if (colorThemeName === 'uniform') {
-                                            this.update({
-                                                colorTheme: {
-                                                    name: colorThemeName,
-                                                    value: this.state.colorValue
-                                                }
-                                            })
-                                        } else {
-                                            this.update({
-                                                colorTheme: { name: colorThemeName }
-                                            })
-                                        }
+                                        this.update({
+                                            colorTheme: {
+                                                name: e.target.value as ColorThemeName,
+                                                value: this.state.colorValue
+                                            }
+                                        })
                                     }}
                                 >
                                     {colorThemeOptions}

+ 9 - 15
src/mol-app/ui/transform/distance-restraint.tsx

@@ -15,7 +15,7 @@ import { Toggle } from '../controls/common';
 import { DistanceRestraintEntity } from 'mol-view/state/entity';
 import { DistanceRestraintUpdate } from 'mol-view/state/transform'
 import { StateContext } from 'mol-view/state/context';
-import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { ColorTheme, SizeTheme, ColorThemeName, ColorThemeNames } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
@@ -23,6 +23,7 @@ import { Unit } from 'mol-model/structure';
 
 export const ColorThemeInfo = {
     'atom-index': {},
+    'carbohydrate-symbol': {},
     'chain-id': {},
     'element-symbol': {},
     'instance-index': {},
@@ -89,7 +90,7 @@ export class DistanceRestraint extends View<Controller<any>, DistanceRestraintSt
             return <option key={name} value={name}>{name}</option>
         })
 
-        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+        const colorThemeOptions = ColorThemeNames.map((name, idx) => {
             return <option key={name} value={name}>{name}</option>
         })
 
@@ -128,19 +129,12 @@ export class DistanceRestraint extends View<Controller<any>, DistanceRestraintSt
                                     className='molstar-form-control'
                                     value={this.state.colorTheme.name}
                                     onChange={(e) => {
-                                        const colorThemeName = e.target.value as ColorThemeInfo
-                                        if (colorThemeName === 'uniform') {
-                                            this.update({
-                                                colorTheme: {
-                                                    name: colorThemeName,
-                                                    value: this.state.colorValue
-                                                }
-                                            })
-                                        } else {
-                                            this.update({
-                                                colorTheme: { name: colorThemeName }
-                                            })
-                                        }
+                                        this.update({
+                                            colorTheme: {
+                                                name: e.target.value as ColorThemeName,
+                                                value: this.state.colorValue
+                                            }
+                                        })
                                     }}
                                 >
                                     {colorThemeOptions}

+ 9 - 15
src/mol-app/ui/transform/spacefill.tsx

@@ -15,7 +15,7 @@ import { Toggle } from '../controls/common';
 import { SpacefillEntity } from 'mol-view/state/entity';
 import { SpacefillUpdate } from 'mol-view/state/transform'
 import { StateContext } from 'mol-view/state/context';
-import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { ColorTheme, SizeTheme, ColorThemeName, ColorThemeNames } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
@@ -23,6 +23,7 @@ import { Unit } from 'mol-model/structure';
 
 export const ColorThemeInfo = {
     'atom-index': {},
+    'carbohydrate-symbol': {},
     'chain-id': {},
     'element-symbol': {},
     'instance-index': {},
@@ -86,7 +87,7 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform
             return <option key={value} value={value}>{value.toString()}</option>
         })
 
-        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+        const colorThemeOptions = ColorThemeNames.map((name, idx) => {
             return <option key={name} value={name}>{name}</option>
         })
 
@@ -137,19 +138,12 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform
                                     className='molstar-form-control'
                                     value={this.state.colorTheme.name}
                                     onChange={(e) => {
-                                        const colorThemeName = e.target.value as ColorThemeInfo
-                                        if (colorThemeName === 'uniform') {
-                                            this.update({
-                                                colorTheme: {
-                                                    name: colorThemeName,
-                                                    value: this.state.colorValue
-                                                }
-                                            })
-                                        } else {
-                                            this.update({
-                                                colorTheme: { name: colorThemeName }
-                                            })
-                                        }
+                                        this.update({
+                                            colorTheme: {
+                                                name: e.target.value as ColorThemeName,
+                                                value: this.state.colorValue
+                                            }
+                                        })
                                     }}
                                 >
                                     {colorThemeOptions}

+ 45 - 62
src/mol-geo/primitive/box.ts

@@ -4,72 +4,55 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-// adapted from three.js, MIT License Copyright 2010-2018 three.js authors
-
 import { Vec3 } from 'mol-math/linear-algebra'
-import { Primitive } from './primitive';
-
-export const DefaultBoxProps = {
-    width: 1,
-    height: 1,
-    depth: 1
-}
-export type BoxProps = Partial<typeof DefaultBoxProps>
-
-const tmpVector = Vec3.zero();
-
-export function Box(props?: BoxProps): Primitive {
-    const { width, height, depth } = { ...DefaultBoxProps, ...props }
-
-    // buffers
-    const vertices = new Float32Array(72);
-    const normals = new Float32Array(72);
-    const indices = new Uint32Array(36);
-
-    // helper variables
-    let vertexCount = 0;
-
-    // build each side of the box geometry
-    buildPlane(2, 1, 0, -1, -1, depth, height, width); // px
-    buildPlane(2, 1, 0, 1, -1, depth, height, -width); // nx
-    buildPlane(0, 2, 1, 1, 1, width, depth, height); // py
-    buildPlane(0, 2, 1, 1, -1, width, depth, -height); // ny
-    buildPlane(0, 1, 2, 1, -1, width, height, depth); // pz
-    buildPlane(0, 1, 2, -1, -1, width, height, -depth); // nz
+import { Primitive, PrimitiveBuilder } from './primitive';
+import { polygon } from './polygon'
 
-    return { vertices, normals, indices }
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero()
+const points = polygon(4, true)
 
-    function buildPlane(u: number, v: number, w: number, udir: number, vdir: number, width: number, height: number, depth: number) {
-        // generate vertices and normals
-        for (let iy = 0; iy < 2; ++iy) {
-            const y = iy * height - height / 2;
-            for (let ix = 0; ix < 2; ++ix) {
-                const x = ix * width - width / 2;
-
-                // set values to correct vector component and add to vertex buffer
-                tmpVector[u] = x * udir;
-                tmpVector[v] = y * vdir;
-                tmpVector[w] = depth / 2;
-                Vec3.toArray(tmpVector, vertices, vertexCount * 3);
+/**
+ * Create a box
+ */
+function createBox(perforated: boolean): Primitive {
+    const builder = PrimitiveBuilder(12)
+
+    // create sides
+    for (let i = 0; i < 4; ++i) {
+        const ni = (i + 1) % 4
+        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)
+        if (!perforated) builder.add(c, d, a)
+    }
 
-                // set values to correct vector component and add to normal buffer
-                tmpVector[u] = 0;
-                tmpVector[v] = 0;
-                tmpVector[w] = depth > 0 ? 1 : -1;
-                Vec3.toArray(tmpVector, normals, vertexCount * 3);
+    // create bases
+    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)
+    if (!perforated) 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)
+    if (!perforated) builder.add(c, d, a)
+
+    return builder.getPrimitive()
+}
 
-                ++vertexCount;
-            }
-        }
+let box: Primitive
+export function Box() {
+    if (!box) box = createBox(false)
+    return box
+}
 
-        // faces
-        const vc = vertexCount - 4
-        const iidx = (vc / 2) * 3
-        indices[iidx] = vc
-        indices[iidx + 1] = vc + 2
-        indices[iidx + 2] = vc + 1
-        indices[iidx + 3] = vc + 2
-        indices[iidx + 4] = vc + 3
-        indices[iidx + 5] = vc + 1
-    }
+let perforatedBox: Primitive
+export function PerforatedBox() {
+    if (!perforatedBox) perforatedBox = createBox(true)
+    return perforatedBox
 }

+ 9 - 2
src/mol-geo/primitive/octahedron.ts

@@ -10,13 +10,20 @@ export const octahedronVertices: ReadonlyArray<number> = [
     0.5, 0, 0,   -0.5, 0, 0,    0, 0.5, 0,
     0, -0.5, 0,     0, 0, 0.5,  0, 0, -0.5
 ];
-
 export const octahedronIndices: ReadonlyArray<number> = [
     0, 2, 4,  0, 4, 3,  0, 3, 5,
     0, 5, 2,  1, 2, 5,  1, 5, 3,
     1, 3, 4,  1, 4, 2
 ];
+export const perforatedOctahedronIndices: ReadonlyArray<number> = [
+    0, 2, 4,   0, 4, 3,
+    // 0, 3, 5,   0, 5, 2,
+    1, 2, 5,   1, 5, 3,
+    // 1, 3, 4,   1, 4, 2
+];
 
 const octahedron = createPrimitive(octahedronVertices, octahedronIndices)
+const perforatedOctahedron = createPrimitive(octahedronVertices, perforatedOctahedronIndices)
 
-export function Octahedron(): Primitive { return octahedron }
+export function Octahedron(): Primitive { return octahedron }
+export function PerforatedOctahedron(): Primitive { return perforatedOctahedron }

+ 12 - 47
src/mol-geo/primitive/prism.ts

@@ -12,12 +12,13 @@ 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 prism with a poligonal base
+ * Create a prism with a poligonal base of 5 or more points
  */
 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
+    if (sideCount < 5) throw new Error('need at least 5 points to build a prism')
+
+    const count = 4 * sideCount
     const builder = PrimitiveBuilder(count)
 
     // create sides
@@ -32,55 +33,19 @@ export function Prism(points: ArrayLike<number>): Primitive {
     }
 
     // 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)
-            builder.add(a, b, on)
-            Vec3.set(a, points[i * 2], points[i * 2 + 1], 0.5)
-            Vec3.set(b, points[ni * 2], points[ni * 2 + 1], 0.5)
-            builder.add(op, b, a)
-        }
+    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)
+        builder.add(a, b, on)
+        Vec3.set(a, points[i * 2], points[i * 2 + 1], 0.5)
+        Vec3.set(b, points[ni * 2], points[ni * 2 + 1], 0.5)
+        builder.add(op, 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, true))
-    return box
-}
-
 let diamond: Primitive
 export function DiamondPrism() {
     if (!diamond) diamond = Prism(polygon(4, false))

+ 28 - 1
src/mol-geo/primitive/pyramid.ts

@@ -5,7 +5,7 @@
  */
 
 import { Vec3 } from 'mol-math/linear-algebra'
-import { Primitive, PrimitiveBuilder } from './primitive';
+import { Primitive, PrimitiveBuilder, createPrimitive } from './primitive';
 import { polygon } from './polygon'
 
 const on = Vec3.create(0, 0, -0.5), op = Vec3.create(0, 0, 0.5)
@@ -57,4 +57,31 @@ let octagonalPyramide: Primitive
 export function OctagonalPyramide() {
     if (!octagonalPyramide) octagonalPyramide = Pyramide(polygon(8, true))
     return octagonalPyramide
+}
+
+//
+
+let perforatedOctagonalPyramide: Primitive
+export function PerforatedOctagonalPyramide() {
+    if (!perforatedOctagonalPyramide) {
+        const points = polygon(8, true)
+        const vertices = new Float32Array(8 * 3 + 6)
+        for (let i = 0; i < 8; ++i) {
+            vertices[i * 3] = points[i * 2]
+            vertices[i * 3 + 1] = points[i * 2 + 1]
+            vertices[i * 3 + 2] = -0.5
+        }
+        vertices[8 * 3] = 0
+        vertices[8 * 3 + 1] = 0
+        vertices[8 * 3 + 2] = -0.5
+        vertices[8 * 3 + 3] = 0
+        vertices[8 * 3 + 4] = 0
+        vertices[8 * 3 + 5] = 0.5
+        const indices: ReadonlyArray<number> = [
+            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)
+    }
+    return perforatedOctagonalPyramide
 }

+ 37 - 111
src/mol-geo/primitive/wedge.ts

@@ -5,118 +5,44 @@
  */
 
 import { Vec3 } from 'mol-math/linear-algebra'
-import { Primitive } from './primitive';
+import { Primitive, PrimitiveBuilder } from './primitive';
+import { polygon } from './polygon'
 
-export const DefaultWedgeProps = {
-    width: 1,
-    height: 1,
-    depth: 1
-}
-export type WedgeProps = Partial<typeof DefaultWedgeProps>
-
-const _a = Vec3.create(0, 0.5, 0.5)
-const _b = Vec3.create(0.5, -0.5, 0.5)
-const _c = Vec3.create(-0.5, -0.5, 0.5)
-const _d = Vec3.create(0, 0.5, -0.5)
-const _e = Vec3.create(0.5, -0.5, -0.5)
-const _f = Vec3.create(-0.5, -0.5, -0.5)
-
-const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero()
-const d = Vec3.zero(), e = Vec3.zero(), f = Vec3.zero()
-
-const nabc = Vec3.create(0, 0, 1)
-const ndef = Vec3.create(0, 0, -1)
-const nabde = Vec3.zero()
-const nbcef = Vec3.create(0, -1, 0)
-const nacdf = Vec3.zero()
-
-const s = Vec3.zero()
-
-export function Wedge(props?: WedgeProps): Primitive {
-    const { width, height, depth } = { ...DefaultWedgeProps, ...props }
-
-    const vertices = new Float32Array(54)
-    const normals = new Float32Array(54)
-    const indices = new Uint32Array(24)
-
-    Vec3.set(s, width, height, depth)
-    Vec3.mul(a, _a, s); Vec3.mul(b, _b, s); Vec3.mul(c, _c, s)
-    Vec3.mul(d, _d, s); Vec3.mul(e, _e, s); Vec3.mul(f, _f, s)
-
-    Vec3.sub(nabde, b, a)
-    Vec3.normalize(nabde, Vec3.set(nabde, -nabde[1], nabde[0], 0))
-    Vec3.sub(nacdf, c, a)
-    Vec3.normalize(nacdf, Vec3.set(nacdf, nacdf[1], -nacdf[0], 0))
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero()
+const points = polygon(3, false)
 
-    let vc = 0
-    let ic = 0
-
-    // abc
-    Vec3.toArray(a, vertices, vc + 0)
-    Vec3.toArray(c, vertices, vc + 3)
-    Vec3.toArray(b, vertices, vc + 6)
-    for (let i = 0; i < 3; ++i) Vec3.toArray(nabc, normals, vc + i * 3)
-    indices[ic + 0] = vc / 3 + 0
-    indices[ic + 1] = vc / 3 + 1
-    indices[ic + 2] = vc / 3 + 2
-    vc += 9
-    ic += 3
-
-    // def
-    Vec3.toArray(d, vertices, vc + 0)
-    Vec3.toArray(e, vertices, vc + 3)
-    Vec3.toArray(f, vertices, vc + 6)
-    for (let i = 0; i < 3; ++i) Vec3.toArray(ndef, normals, vc + i * 3)
-    indices[ic + 0] = vc / 3 + 0
-    indices[ic + 1] = vc / 3 + 1
-    indices[ic + 2] = vc / 3 + 2
-    vc += 9
-    ic += 3
-
-    // abde
-    Vec3.toArray(a, vertices, vc + 0)
-    Vec3.toArray(d, vertices, vc + 3)
-    Vec3.toArray(e, vertices, vc + 6)
-    Vec3.toArray(b, vertices, vc + 9)
-    for (let i = 0; i < 4; ++i) Vec3.toArray(nabde, normals, vc + i * 3)
-    indices[ic + 0] = vc / 3 + 2
-    indices[ic + 1] = vc / 3 + 1
-    indices[ic + 2] = vc / 3 + 0
-    indices[ic + 3] = vc / 3 + 0
-    indices[ic + 4] = vc / 3 + 3
-    indices[ic + 5] = vc / 3 + 2
-    vc += 12
-    ic += 6
-
-    // acdf
-    Vec3.toArray(d, vertices, vc + 0)
-    Vec3.toArray(a, vertices, vc + 3)
-    Vec3.toArray(c, vertices, vc + 6)
-    Vec3.toArray(f, vertices, vc + 9)
-    for (let i = 0; i < 4; ++i) Vec3.toArray(nacdf, normals, vc + i * 3)
-    indices[ic + 0] = vc / 3 + 2
-    indices[ic + 1] = vc / 3 + 1
-    indices[ic + 2] = vc / 3 + 0
-    indices[ic + 3] = vc / 3 + 0
-    indices[ic + 4] = vc / 3 + 3
-    indices[ic + 5] = vc / 3 + 2
-    vc += 12
-    ic += 6
-
-    // bcef
-    Vec3.toArray(e, vertices, vc + 0)
-    Vec3.toArray(f, vertices, vc + 3)
-    Vec3.toArray(c, vertices, vc + 6)
-    Vec3.toArray(b, vertices, vc + 9)
-    for (let i = 0; i < 4; ++i) Vec3.toArray(nbcef, normals, vc + i * 3)
-    indices[ic + 0] = vc / 3 + 2
-    indices[ic + 1] = vc / 3 + 1
-    indices[ic + 2] = vc / 3 + 0
-    indices[ic + 3] = vc / 3 + 0
-    indices[ic + 4] = vc / 3 + 3
-    indices[ic + 5] = vc / 3 + 2
-    vc += 12
-    ic += 6
+/**
+ * Create a prism with a poligonal base
+ */
+export function createWedge(): Primitive {
+    const builder = PrimitiveBuilder(8)
+
+    // create sides
+    for (let i = 0; i < 3; ++i) {
+        const ni = (i + 1) % 3
+        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
+    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)
+
+    return builder.getPrimitive()
+}
 
-    return { vertices, normals, indices }
+let wedge: Primitive
+export function Wedge() {
+    if (!wedge) wedge = createWedge()
+    return wedge
 }

+ 4 - 0
src/mol-geo/representation/structure/index.ts

@@ -35,6 +35,7 @@ export function StructureRepresentation<P extends StructureProps>(visualCtor: ()
 
     function create(structure: Structure, props: P = {} as P) {
         _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
+        _props.colorTheme!.structure = structure
 
         return Task.create('Creating StructureRepresentation', async ctx => {
             if (!_structure) {
@@ -56,6 +57,7 @@ export function StructureRepresentation<P extends StructureProps>(visualCtor: ()
     function update(props: P) {
         return Task.create('Updating StructureRepresentation', async ctx => {
             _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
+            _props.colorTheme!.structure = _structure
 
             if (!await visual.update(ctx, _props)) {
                 await visual.create(ctx, _structure, _props)
@@ -98,6 +100,7 @@ export function StructureUnitsRepresentation<P extends StructureProps>(visualCto
 
     function create(structure: Structure, props: P = {} as P) {
         _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
+        _props.colorTheme!.structure = structure
 
         return Task.create('Creating StructureRepresentation', async ctx => {
             if (!_structure) {
@@ -151,6 +154,7 @@ export function StructureUnitsRepresentation<P extends StructureProps>(visualCto
     function update(props: P) {
         return Task.create('Updating StructureRepresentation', async ctx => {
             _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
+            _props.colorTheme!.structure = _structure
 
             visuals.forEach(async ({ visual, group }) => {
                 if (!await visual.update(ctx, _props)) {

+ 5 - 6
src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts

@@ -78,14 +78,13 @@ export function CarbohydrateLinkVisual(): StructureVisual<CarbohydrateLinkProps>
             currentStructure = structure
 
             const { colorTheme } = { ...DefaultCarbohydrateLinkProps, ...props }
+            const elementCount = structure.carbohydrates.links.length
             const instanceCount = 1
-            const elementCount = currentStructure.elementCount
 
             mesh = await createCarbohydrateLinkCylinderMesh(ctx, currentStructure, currentProps, mesh)
-            // console.log(mesh)
 
             const transforms = createIdentityTransform()
-            const color = createColors(createCarbohydrateLinkIterator(structure), colorTheme)
+            const color = createColors(CarbohydrateLinkIterator(structure), colorTheme)
             const marker = createMarkers(instanceCount * elementCount)
 
             const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
@@ -114,7 +113,7 @@ export function CarbohydrateLinkVisual(): StructureVisual<CarbohydrateLinkProps>
             }
 
             if (updateColor) {
-                createColors(createCarbohydrateLinkIterator(currentStructure), newProps.colorTheme, renderObject.values)
+                createColors(CarbohydrateLinkIterator(currentStructure), newProps.colorTheme, renderObject.values)
             }
 
             updateMeshValues(renderObject.values, newProps)
@@ -135,7 +134,7 @@ export function CarbohydrateLinkVisual(): StructureVisual<CarbohydrateLinkProps>
     }
 }
 
-function createCarbohydrateLinkIterator(structure: Structure): LocationIterator {
+function CarbohydrateLinkIterator(structure: Structure): LocationIterator {
     const { elements, links } = structure.carbohydrates
     const elementCount = links.length
     const instanceCount = 1
@@ -178,7 +177,7 @@ function markLink(loci: Loci, action: MarkerAction, structure: Structure, values
     const tMarker = values.tMarker
 
     const { getLinkIndex } = structure.carbohydrates
-    const elementCount = structure.carbohydrates.elements.length
+    const elementCount = structure.carbohydrates.links.length
 
     let changed = false
     const array = tMarker.ref.value.array

+ 26 - 17
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -47,7 +47,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
         Mat4.targetTo(t, center, pd, normal)
         Mat4.setTranslation(t, center)
 
-        builder.setId(i)
+        builder.setId(i * 2)
 
         switch (shapeType) {
             case SaccharideShapes.FilledSphere:
@@ -58,18 +58,22 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
                 builder.addBox(t)
                 break;
             case SaccharideShapes.CrossedCube:
-                // TODO split
                 Mat4.scaleUniformly(t, t, side)
-                builder.addBox(t)
+                builder.addPerforatedBox(t)
+                Mat4.mul(t, t, Mat4.rotZ90X180)
+                builder.setId(i * 2 + 1)
+                builder.addPerforatedBox(t)
                 break;
             case SaccharideShapes.FilledCone:
                 Mat4.scaleUniformly(t, t, side * 1.2)
                 builder.addOctagonalPyramid(t)
                 break
             case SaccharideShapes.DevidedCone:
-                // TODO split
                 Mat4.scaleUniformly(t, t, side * 1.2)
-                builder.addOctagonalPyramid(t)
+                builder.addPerforatedOctagonalPyramid(t)
+                Mat4.mul(t, t, Mat4.rotZ90)
+                builder.setId(i * 2 + 1)
+                builder.addPerforatedOctagonalPyramid(t)
                 break
             case SaccharideShapes.FlatBox:
                 Mat4.mul(t, t, Mat4.rotZY90)
@@ -86,10 +90,12 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
                 builder.addOctahedron(t)
                 break
             case SaccharideShapes.DividedDiamond:
-                // TODO split
                 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.addPerforatedOctahedron(t)
+                Mat4.mul(t, t, Mat4.rotY90)
+                builder.setId(i * 2 + 1)
+                builder.addPerforatedOctahedron(t)
                 break
             case SaccharideShapes.FlatDiamond:
                 Mat4.mul(t, t, Mat4.rotZY90)
@@ -141,7 +147,7 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr
             mesh = await createCarbohydrateSymbolMesh(ctx, currentStructure, mesh)
 
             const transforms = createIdentityTransform()
-            const color = createColors(createCarbohydrateElementIterator(structure), colorTheme)
+            const color = createColors(CarbohydrateElementIterator(structure), colorTheme)
             const marker = createMarkers(instanceCount * elementCount)
 
             const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
@@ -170,7 +176,7 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr
             }
 
             if (updateColor) {
-                createColors(createCarbohydrateElementIterator(currentStructure), newProps.colorTheme, renderObject.values)
+                createColors(CarbohydrateElementIterator(currentStructure), newProps.colorTheme, renderObject.values)
             }
 
             updateMeshValues(renderObject.values, newProps)
@@ -191,24 +197,27 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr
     }
 }
 
-function createCarbohydrateElementIterator(structure: Structure): LocationIterator {
+function CarbohydrateElementIterator(structure: Structure): LocationIterator {
     const carbElements = structure.carbohydrates.elements
-    const elementCount = carbElements.length
+    const elementCount = carbElements.length * 2
     const instanceCount = 1
     const location = StructureElement.create()
-    const getLocation = (elementIndex: number, instanceIndex: number) => {
-        const carb = carbElements[elementIndex]
+    function getLocation (elementIndex: number, instanceIndex: number) {
+        const carb = carbElements[Math.floor(elementIndex / 2)]
         location.unit = carb.unit
         location.element = carb.anomericCarbon
         return location
     }
-    return LocationIterator(elementCount, instanceCount, getLocation)
+    function isSecondary (elementIndex: number, instanceIndex: number) {
+        return (elementIndex % 2) === 1
+    }
+    return LocationIterator(elementCount, instanceCount, getLocation, isSecondary)
 }
 
 function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: number) {
     const { objectId, elementId } = pickingId
     if (id === objectId) {
-        const carb = structure.carbohydrates.elements[elementId]
+        const carb = structure.carbohydrates.elements[Math.floor(elementId / 2)]
         const { unit } = carb
         const index = OrderedSet.findPredecessorIndex(unit.elements, carb.anomericCarbon)
         const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex)
@@ -221,7 +230,7 @@ function markCarbohydrate(loci: Loci, action: MarkerAction, structure: Structure
     const tMarker = values.tMarker
 
     const { getElementIndex } = structure.carbohydrates
-    const elementCount = structure.carbohydrates.elements.length
+    const elementCount = structure.carbohydrates.elements.length * 2
 
     let changed = false
     const array = tMarker.ref.value.array
@@ -234,7 +243,7 @@ function markCarbohydrate(loci: Loci, action: MarkerAction, structure: Structure
             OrderedSet.forEach(e.indices, index => {
                 const idx = getElementIndex(e.unit, e.unit.elements[index])
                 if (idx !== undefined) {
-                    if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                    if (applyMarkerAction(array, idx * 2, idx * 2 + 2, action) && !changed) {
                         changed = true
                     }
                 }

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

@@ -39,7 +39,7 @@ export function createColors(locationIt: LocationIterator, props: ColorTheme, co
         case 'atom-index':
             return elementIndexColorData(locationIt, colorData)
         case 'carbohydrate-symbol':
-            return carbohydrateSymbolColorData(locationIt, colorData)
+            return carbohydrateSymbolColorData(locationIt, props, colorData)
         case 'chain-id':
             return chainIdColorData(locationIt, colorData)
         case 'element-symbol':
@@ -47,7 +47,7 @@ export function createColors(locationIt: LocationIterator, props: ColorTheme, co
         case 'instance-index':
             return instanceIndexColorData(locationIt, colorData)
         case 'uniform':
-            return createUniformColor(locationIt, () => props.value, colorData)
+            return createUniformColor(locationIt, () => props.value || 0x000000, colorData)
     }
 }
 

+ 10 - 5
src/mol-geo/representation/structure/visual/util/location-iterator.ts

@@ -13,13 +13,15 @@ export interface LocationValue {
     index: number
     elementIndex: number
     instanceIndex: number
+    isSecondary: boolean
 }
 
-export const NullLocationValue = {
+export const NullLocationValue: LocationValue = {
     location: NullLocation,
     index: 0,
     elementIndex: 0,
-    instanceIndex: 0
+    instanceIndex: 0,
+    isSecondary: false
 }
 
 export interface LocationIterator extends Iterator<LocationValue> {
@@ -32,13 +34,15 @@ export interface LocationIterator extends Iterator<LocationValue> {
 }
 
 type LocationGetter = (elementIndex: number, instanceIndex: number) => Location
+type IsSecondaryGetter = (elementIndex: number, instanceIndex: number) => boolean
 
-export function LocationIterator(elementCount: number, instanceCount: number, getLocation: LocationGetter): LocationIterator {
-    const value = {
+export function LocationIterator(elementCount: number, instanceCount: number, getLocation: LocationGetter, isSecondary: IsSecondaryGetter = () => false): LocationIterator {
+    const value: LocationValue = {
         location: NullLocation as Location,
         index: 0,
         elementIndex: 0,
-        instanceIndex: 0
+        instanceIndex: 0,
+        isSecondary: false
     }
 
     let hasNext = value.elementIndex < elementCount
@@ -57,6 +61,7 @@ export function LocationIterator(elementCount: number, instanceCount: number, ge
                 value.instanceIndex = instanceIndex
                 value.index = instanceIndex * elementCount + elementIndex
                 value.location = getLocation(elementIndex, instanceIndex)
+                value.isSecondary = isSecondary(elementIndex, instanceIndex)
                 ++elementIndex
                 if (elementIndex === elementCount) {
                     ++instanceIndex

+ 20 - 3
src/mol-geo/shape/mesh-builder.ts

@@ -16,10 +16,12 @@ import { getNormalMatrix } from '../util';
 import { addSheet } from '../primitive/sheet';
 import { addTube } from '../primitive/tube';
 import { StarProps, Star } from '../primitive/star';
-import { Octahedron } from '../primitive/octahedron';
+import { Octahedron, PerforatedOctahedron } from '../primitive/octahedron';
 import { Primitive } from '../primitive/primitive';
-import { Wedge, Box, DiamondPrism, PentagonalPrism, HexagonalPrism } from '../primitive/prism';
-import { OctagonalPyramide } from '../primitive/pyramid';
+import { DiamondPrism, PentagonalPrism, HexagonalPrism } from '../primitive/prism';
+import { OctagonalPyramide, PerforatedOctagonalPyramide } from '../primitive/pyramid';
+import { PerforatedBox, Box } from '../primitive/box';
+import { Wedge } from '../primitive/wedge';
 
 export interface MeshBuilderState {
     vertices: ChunkedArray<number, 3>
@@ -30,14 +32,17 @@ export interface MeshBuilderState {
 export interface MeshBuilder {
     add(t: Mat4, _vertices: ArrayLike<number>, _normals: ArrayLike<number>, _indices?: ArrayLike<number>): void
     addBox(t: Mat4): void
+    addPerforatedBox(t: Mat4): void
     addPlane(t: Mat4, props?: PlaneProps): 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
@@ -140,6 +145,10 @@ export namespace MeshBuilder {
                 const { vertices, normals, indices } = Box()
                 add(t, vertices, normals, indices)
             },
+            addPerforatedBox: (t: Mat4) => {
+                const { vertices, normals, indices } = PerforatedBox()
+                add(t, vertices, normals, indices)
+            },
             addPlane: (t: Mat4, props?: PlaneProps) => {
                 const { vertices, normals, indices } = Plane(props)
                 add(t, vertices, normals, indices)
@@ -164,6 +173,10 @@ export namespace MeshBuilder {
                 const { vertices, normals, indices } = OctagonalPyramide()
                 add(t, vertices, normals, indices)
             },
+            addPerforatedOctagonalPyramid: (t: Mat4) => {
+                const { vertices, normals, indices } = PerforatedOctagonalPyramide()
+                add(t, vertices, normals, indices)
+            },
             addStar: (t: Mat4, props?: StarProps) => {
                 const { vertices, normals, indices } = Star(props)
                 add(t, vertices, normals, indices)
@@ -172,6 +185,10 @@ export namespace MeshBuilder {
                 const { vertices, normals, indices } = Octahedron()
                 add(t, vertices, normals, indices)
             },
+            addPerforatedOctahedron: (t: Mat4) => {
+                const { vertices, normals, indices } = PerforatedOctahedron()
+                add(t, vertices, normals, indices)
+            },
             addCylinder: (start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps) => {
                 const d = Vec3.distance(start, end) * lengthScale
                 props.height = d

+ 14 - 11
src/mol-geo/theme/index.ts

@@ -5,22 +5,25 @@
  */
 
 import { Color } from 'mol-util/color';
+import { Structure } from 'mol-model/structure';
 
-export interface UniformColorTheme {
-    name: 'uniform'
-    value: Color
-}
-
-export interface ScaleColorTheme {
-    name:  'atom-index' | 'chain-id'| 'instance-index'
+export interface ColorTheme {
+    name: 'atom-index' | 'chain-id'| 'instance-index' | 'uniform' | 'carbohydrate-symbol' | 'element-symbol'
     domain?: [number, number]
+    value?: Color
+    structure?: Structure
 }
 
-export interface TableColorTheme {
-    name:  'carbohydrate-symbol' | 'element-symbol'
+export const ColorThemeInfo = {
+    'atom-index': {},
+    'carbohydrate-symbol': {},
+    'chain-id': {},
+    'element-symbol': {},
+    'instance-index': {},
+    'uniform': {}
 }
-
-export type ColorTheme = UniformColorTheme | ScaleColorTheme | TableColorTheme
+export type ColorThemeName = keyof typeof ColorThemeInfo
+export const ColorThemeNames = Object.keys(ColorThemeInfo)
 
 export interface UniformSizeTheme {
     name: 'uniform',

+ 36 - 30
src/mol-geo/theme/structure/color/carbohydrate-symbol.ts

@@ -4,41 +4,47 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/structure';
+import { StructureElement, Link, ElementIndex, Unit } from 'mol-model/structure';
 
 import { ColorData, createElementColor } from '../../../util/color-data';
-import { ColorScale, Color } from 'mol-util/color';
+import { Color } from 'mol-util/color';
 import { LocationIterator, LocationValue } from '../../../representation/structure/visual/util/location-iterator';
+import { ColorTheme } from '../..';
+import { SaccharideColors } from 'mol-model/structure/structure/carbohydrates/constants';
 
-function getAsymId(unit: Unit): StructureElement.Property<string> {
-    switch (unit.kind) {
-        case Unit.Kind.Atomic:
-            return StructureProperties.chain.label_asym_id
-        case Unit.Kind.Spheres:
-        case Unit.Kind.Gaussians:
-            return StructureProperties.coarse.asym_id
-    }
-}
-
-export function carbohydrateSymbolColorData(locationIt: LocationIterator, colorData?: ColorData) {
-    const l = StructureElement.create()
-
-    function colorFn(locationValue: LocationValue): Color {
-        const { location } = locationValue
-        if (StructureElement.isLocation(location)) {
-            const map = location.unit.model.properties.asymIdSerialMap
-            const scale = ColorScale.create({ domain: [ 0, map.size - 1 ] })
-            const asym_id = getAsymId(location.unit)
-            return scale.color(map.get(asym_id(location)) || 0)
-        } else if (Link.isLocation(location)) {
-            const map = location.aUnit.model.properties.asymIdSerialMap
-            const scale = ColorScale.create({ domain: [ 0, map.size - 1 ] })
-            const asym_id = getAsymId(location.aUnit)
-            l.unit = location.aUnit
-            l.element = location.aUnit.elements[location.aIndex]
-            return scale.color(map.get(asym_id(l)) || 0)
+const DefaultColor = 0xCCCCCC;
+
+export function carbohydrateSymbolColorData(locationIt: LocationIterator, props: ColorTheme, colorData?: ColorData) {
+    let colorFn: (locationValue: LocationValue) => Color
+
+    if (props.structure) {
+        const { elements, getElementIndex, getAnomericCarbon } = props.structure.carbohydrates
+
+        const getColor = (unit: Unit, index: ElementIndex) => {
+            const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index[index]
+            const anomericCarbon = getAnomericCarbon(unit, residueIndex)
+            if (anomericCarbon !== undefined) {
+                const idx = getElementIndex(unit, anomericCarbon)
+                if (idx !== undefined) return elements[idx].component.color
+            }
+            return DefaultColor
+        }
+
+        colorFn = (locationValue: LocationValue) => {
+            const { location: l } = locationValue
+            if (locationValue.isSecondary) {
+                return SaccharideColors.Secondary
+            } else {
+                if (StructureElement.isLocation(l)) {
+                    return getColor(l.unit, l.element)
+                } else if (Link.isLocation(l)) {
+                    return getColor(l.aUnit, l.aUnit.elements[l.aIndex])
+                }
+            }
+            return DefaultColor
         }
-        return 0
+    } else {
+        colorFn = () => DefaultColor
     }
 
     return createElementColor(locationIt, colorFn, colorData)

+ 14 - 6
src/mol-math/linear-algebra/3d/mat4.ts

@@ -873,18 +873,26 @@ namespace Mat4 {
         return out;
     }
 
-    /** Rotation matrix for 90deg rotation around x-axis */
+    /** Rotation matrix for 90deg around x-axis */
     export const rotX90: ReadonlyMat4 = Mat4.fromRotation(Mat4.identity(), degToRad(90), Vec3.create(1, 0, 0))
-    /** Rotation matrix for 90deg rotation around y-axis */
+    /** Rotation matrix for 180deg around x-axis */
+    export const rotX180: ReadonlyMat4 = Mat4.fromRotation(Mat4.identity(), degToRad(180), Vec3.create(1, 0, 0))
+    /** Rotation matrix for 90deg around y-axis */
     export const rotY90: ReadonlyMat4 = Mat4.fromRotation(Mat4.identity(), degToRad(90), Vec3.create(0, 1, 0))
-    /** Rotation matrix for 90deg rotation around z-axis */
+    /** Rotation matrix for 180deg around y-axis */
+    export const rotY180: ReadonlyMat4 = Mat4.fromRotation(Mat4.identity(), degToRad(180), Vec3.create(0, 1, 0))
+    /** Rotation matrix for 90deg around z-axis */
     export const rotZ90: ReadonlyMat4 = Mat4.fromRotation(Mat4.identity(), degToRad(90), Vec3.create(0, 0, 1))
-    /** Rotation matrix for 90deg rotation around first x-axis and then y-axis */
+    /** Rotation matrix for 180deg around z-axis */
+    export const rotZ180: ReadonlyMat4 = Mat4.fromRotation(Mat4.identity(), degToRad(180), Vec3.create(0, 0, 1))
+    /** Rotation matrix for 90deg around first x-axis and then y-axis */
     export const rotXY90: ReadonlyMat4 = Mat4.mul(Mat4.identity(), rotX90, rotY90)
-    /** Rotation matrix for 90deg rotation around first z-axis and then y-axis */
+    /** Rotation matrix for 90deg around first z-axis and then y-axis */
     export const rotZY90: ReadonlyMat4 = Mat4.mul(Mat4.identity(), rotZ90, rotY90)
-    /** Rotation matrix for 90deg rotation around first z-axis and then y-axis and then z-axis */
+    /** Rotation matrix for 90deg around first z-axis and then y-axis and then z-axis */
     export const rotZYZ90: ReadonlyMat4 = Mat4.mul(Mat4.identity(), rotZY90, rotZ90)
+    /** Rotation matrix for 90deg around first z-axis and then 180deg around x-axis */
+    export const rotZ90X180: ReadonlyMat4 = Mat4.mul(Mat4.identity(), rotZ90, rotX180)
 }
 
 export default Mat4

+ 18 - 1
src/mol-model/structure/structure/carbohydrates/compute.ts

@@ -315,5 +315,22 @@ function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[
         return linkMap.get(linkKey(unitA, anomericCarbonA, unitB, anomericCarbonB))
     }
 
-    return { getElementIndex, getLinkIndex }
+    // anomeric carbon lookup
+
+    function anomericCarbonKey(unit: Unit, residueIndex: ResidueIndex) {
+        return `${unit.id}|${residueIndex}`
+    }
+
+    const anomericCarbonMap = new Map<string, ElementIndex>()
+    for (let i = 0, il = elements.length; i < il; ++i) {
+        const { unit, anomericCarbon } = elements[i]
+        const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index[anomericCarbon]
+        anomericCarbonMap.set(anomericCarbonKey(unit, residueIndex), anomericCarbon)
+    }
+
+    function getAnomericCarbon(unit: Unit, residueIndex: ResidueIndex) {
+        return anomericCarbonMap.get(anomericCarbonKey(unit, residueIndex))
+    }
+
+    return { getElementIndex, getLinkIndex, getAnomericCarbon }
 }

+ 1 - 2
src/mol-model/structure/structure/carbohydrates/constants.ts

@@ -12,8 +12,7 @@ export const enum SaccharideShapes {
     FlatBox, FilledStar, FilledDiamond, FlatDiamond, FlatHexagon, Pentagon
 }
 
-// TODO move to theme
-const enum SaccharideColors {
+export const enum SaccharideColors {
     Blue = 0x0090bc,
     Green =	0x00a651,
     Yellow = 0xffd400,

+ 1 - 0
src/mol-model/structure/structure/carbohydrates/data.ts

@@ -44,4 +44,5 @@ export interface Carbohydrates {
     partialElements: ReadonlyArray<PartialCarbohydrateElement>
     getElementIndex: (unit: Unit, anomericCarbon: ElementIndex) => number | undefined
     getLinkIndex: (unitA: Unit, anomericCarbonA: ElementIndex, unitB: Unit, anomericCarbonB: ElementIndex) => number | undefined
+    getAnomericCarbon: (unit: Unit, residueIndex: ResidueIndex) => ElementIndex | undefined
 }

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

@@ -78,7 +78,7 @@ export class Stage {
         // this.loadPdbid('1hrv') // viral assembly
         // this.loadPdbid('1rb8') // virus
         // this.loadPdbid('1blu') // metal coordination
-        this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein
+        // this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein
         // this.loadPdbid('4v5a') // ribosome
         // this.loadPdbid('3j3q') // ...
         // this.loadPdbid('2np2') // dna
@@ -98,7 +98,7 @@ 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('3ina') // contains GlcN and IdoA
         // this.loadPdbid('1umz') // contains Xyl (Xyloglucan)
         // this.loadPdbid('1mfb') // contains Abe
         // this.loadPdbid('2gdu') // contains sucrose