Browse Source

wip, lines

Alexander Rose 6 years ago
parent
commit
230c5c0d93

+ 5 - 3
src/mol-geo/geometry/geometry.ts

@@ -5,7 +5,7 @@
  */
 
 import { Mesh } from './mesh/mesh';
-import { Point } from './point/point';
+import { Points } from './points/points';
 import { RenderableState } from 'mol-gl/renderable';
 import { ValueCell } from 'mol-util';
 import { BaseValues } from 'mol-gl/renderable/schema';
@@ -14,8 +14,9 @@ import { ColorThemeProps } from 'mol-view/theme/color';
 import { LocationIterator } from '../util/location-iterator';
 import { ColorType } from './color-data';
 import { SizeType } from './size-data';
+import { Lines } from './lines/lines';
 
-export type GeometryKindType = { 'mesh': Mesh, 'point': Point }
+export type GeometryKindType = { 'mesh': Mesh, 'points': Points, 'lines': Lines }
 export type GeometryKind = keyof GeometryKindType
 export type Geometry = Helpers.ValueOf<GeometryKindType>
 
@@ -23,7 +24,8 @@ export namespace Geometry {
     export function getDrawCount(geometry: Geometry) {
         switch (geometry.kind) {
             case 'mesh': return geometry.triangleCount * 3
-            case 'point': return geometry.vertexCount
+            case 'points': return geometry.pointCount
+            case 'lines': return geometry.lineCount
         }
     }
 

+ 0 - 1
src/mol-geo/geometry/line/line-builder.ts

@@ -1 +0,0 @@
-// TODO

+ 0 - 1
src/mol-geo/geometry/line/line.ts

@@ -1 +0,0 @@
-// TODO

+ 60 - 0
src/mol-geo/geometry/lines/lines-builder.ts

@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+import { ChunkedArray } from 'mol-data/util';
+import { Lines } from './lines';
+
+export interface LinesBuilder {
+    add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number): void
+    getLines(): Lines
+}
+
+export namespace LinesBuilder {
+    export function create(initialCount = 2048, chunkSize = 1024, lines?: Lines): LinesBuilder {
+        const mappings = ChunkedArray.create(Float32Array, 2, chunkSize, lines ? lines.mappingBuffer.ref.value : initialCount);
+        const groups = ChunkedArray.create(Float32Array, 1, chunkSize, lines ? lines.groupBuffer.ref.value : initialCount);
+        const indices = ChunkedArray.create(Uint32Array, 3, chunkSize * 3, lines ? lines.indexBuffer.ref.value : initialCount * 3);
+        const starts = ChunkedArray.create(Float32Array, 3, chunkSize, lines ? lines.startBuffer.ref.value : initialCount);
+        const ends = ChunkedArray.create(Float32Array, 3, chunkSize, lines ? lines.endBuffer.ref.value : initialCount);
+
+        return {
+            add: (startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number) => {
+                const offset = mappings.elementCount
+                for (let i = 0; i < 4; ++i) {
+                    ChunkedArray.add3(starts, startX, startY, startZ);
+                    ChunkedArray.add3(ends, endX, endY, endZ);
+                    ChunkedArray.add(groups, group);
+                }
+                ChunkedArray.add2(mappings, -1, 1);
+                ChunkedArray.add2(mappings, -1, -1);
+                ChunkedArray.add2(mappings, 1, 1);
+                ChunkedArray.add2(mappings, 1, -1);
+                // ChunkedArray.add3(indices, offset, offset + 1, offset + 2);
+                // ChunkedArray.add3(indices, offset + 1, offset + 3, offset + 2);
+                ChunkedArray.add3(indices, offset + 2, offset + 1, offset);
+                ChunkedArray.add3(indices, offset + 2, offset + 3, offset + 1);
+            },
+            getLines: () => {
+                const mb = ChunkedArray.compact(mappings, true) as Float32Array
+                const ib = ChunkedArray.compact(indices, true) as Uint32Array
+                const gb = ChunkedArray.compact(groups, true) as Float32Array
+                const sb = ChunkedArray.compact(starts, true) as Float32Array
+                const eb = ChunkedArray.compact(ends, true) as Float32Array
+                console.log(indices.elementCount, mappings.elementCount, groups.elementCount)
+                return {
+                    kind: 'lines',
+                    lineCount: indices.elementCount / 2,
+                    mappingBuffer: lines ? ValueCell.update(lines.mappingBuffer, mb) : ValueCell.create(mb),
+                    indexBuffer: lines ? ValueCell.update(lines.indexBuffer, ib) : ValueCell.create(ib),
+                    groupBuffer: lines ? ValueCell.update(lines.groupBuffer, gb) : ValueCell.create(gb),
+                    startBuffer: lines ? ValueCell.update(lines.startBuffer, sb) : ValueCell.create(sb),
+                    endBuffer: lines ? ValueCell.update(lines.endBuffer, eb) : ValueCell.create(eb),
+                }
+            }
+        }
+    }
+}

+ 107 - 0
src/mol-geo/geometry/lines/lines.ts

@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util'
+import { Mat4 } from 'mol-math/linear-algebra'
+import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
+import { Geometry } from '../geometry';
+import { RuntimeContext } from 'mol-task';
+import { createColors } from '../color-data';
+import { createMarkers } from '../marker-data';
+import { createSizes } from '../size-data';
+import { TransformData } from '../transform-data';
+import { LocationIterator } from '../../util/location-iterator';
+import { SizeThemeProps } from 'mol-view/theme/size';
+import { LinesValues } from 'mol-gl/renderable/lines';
+
+/** Wide line */
+export interface Lines {
+    readonly kind: 'lines',
+    /** Number of lines */
+    lineCount: number,
+    /** Mapping buffer as array of xy values wrapped in a value cell */
+    readonly mappingBuffer: ValueCell<Float32Array>,
+    /** Index buffer as array of vertex index triplets wrapped in a value cell */
+    readonly indexBuffer: ValueCell<Uint32Array>,
+    /** Group buffer as array of group ids for each vertex wrapped in a value cell */
+    readonly groupBuffer: ValueCell<Float32Array>,
+    /** Line start buffer as array of xyz values wrapped in a value cell */
+    readonly startBuffer: ValueCell<Float32Array>,
+    /** Line end buffer as array of xyz values wrapped in a value cell */
+    readonly endBuffer: ValueCell<Float32Array>,
+}
+
+export namespace Lines {
+    export function createEmpty(lines?: Lines): Lines {
+        const mb = lines ? lines.mappingBuffer.ref.value : new Float32Array(0)
+        const ib = lines ? lines.indexBuffer.ref.value : new Uint32Array(0)
+        const gb = lines ? lines.groupBuffer.ref.value : new Float32Array(0)
+        const sb = lines ? lines.startBuffer.ref.value : new Float32Array(0)
+        const eb = lines ? lines.endBuffer.ref.value : new Float32Array(0)
+        return {
+            kind: 'lines',
+            lineCount: 0,
+            mappingBuffer: lines ? ValueCell.update(lines.mappingBuffer, mb) : ValueCell.create(mb),
+            indexBuffer: lines ? ValueCell.update(lines.indexBuffer, ib) : ValueCell.create(ib),
+            groupBuffer: lines ? ValueCell.update(lines.groupBuffer, gb) : ValueCell.create(gb),
+            startBuffer: lines ? ValueCell.update(lines.startBuffer, sb) : ValueCell.create(sb),
+            endBuffer: lines ? ValueCell.update(lines.endBuffer, eb) : ValueCell.create(eb),
+        }
+    }
+
+    export function transformImmediate(line: Lines, t: Mat4) {
+        transformRangeImmediate(line, t, 0, line.lineCount)
+    }
+
+    export function transformRangeImmediate(lines: Lines, t: Mat4, offset: number, count: number) {
+        const start = lines.startBuffer.ref.value
+        transformPositionArray(t, start, offset, count * 4)
+        ValueCell.update(lines.startBuffer, start);
+        const end = lines.endBuffer.ref.value
+        transformPositionArray(t, end, offset, count * 4)
+        ValueCell.update(lines.endBuffer, end);
+    }
+
+    //
+
+    export const DefaultProps = {
+        ...Geometry.DefaultProps,
+        lineSizeAttenuation: false,
+        sizeTheme: { name: 'uniform', value: 1 } as SizeThemeProps,
+    }
+    export type Props = typeof DefaultProps
+
+    export async function createValues(ctx: RuntimeContext, lines: Lines, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<LinesValues> {
+        const { instanceCount, groupCount } = locationIt
+        const color = await createColors(ctx, locationIt, props.colorTheme)
+        const size = await createSizes(ctx, locationIt, props.sizeTheme)
+        const marker = createMarkers(instanceCount * groupCount)
+
+        const counts = { drawCount: lines.lineCount * 2 * 3, groupCount, instanceCount }
+
+        return {
+            aMapping: lines.mappingBuffer,
+            aGroup: lines.groupBuffer,
+            aStart: lines.startBuffer,
+            aEnd: lines.endBuffer,
+            elements: lines.indexBuffer,
+            ...color,
+            ...size,
+            ...marker,
+            ...transform,
+
+            ...Geometry.createValues(props, counts),
+            dLineSizeAttenuation: ValueCell.create(props.lineSizeAttenuation),
+            dDoubleSided: ValueCell.create(true),
+            dFlipSided: ValueCell.create(false),
+        }
+    }
+
+    export function updateValues(values: LinesValues, props: Props) {
+        Geometry.updateValues(values, props)
+        ValueCell.updateIfChanged(values.dLineSizeAttenuation, props.lineSizeAttenuation)
+    }
+}

+ 12 - 12
src/mol-geo/geometry/point/point-builder.ts → src/mol-geo/geometry/points/points-builder.ts

@@ -6,31 +6,31 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 import { ChunkedArray } from 'mol-data/util';
-import { Point } from './point';
+import { Points } from './points';
 
-export interface PointBuilder {
+export interface PointsBuilder {
     add(x: number, y: number, z: number, group: number): void
-    getPoint(): Point
+    getPoints(): Points
 }
 
-export namespace PointBuilder {
-    export function create(initialCount = 2048, chunkSize = 1024, point?: Point): PointBuilder {
-        const vertices = ChunkedArray.create(Float32Array, 3, chunkSize, point ? point.vertexBuffer.ref.value : initialCount);
-        const groups = ChunkedArray.create(Float32Array, 1, chunkSize, point ? point.groupBuffer.ref.value : initialCount);
+export namespace PointsBuilder {
+    export function create(initialCount = 2048, chunkSize = 1024, points?: Points): PointsBuilder {
+        const vertices = ChunkedArray.create(Float32Array, 3, chunkSize, points ? points.centerBuffer.ref.value : initialCount);
+        const groups = ChunkedArray.create(Float32Array, 1, chunkSize, points ? points.groupBuffer.ref.value : initialCount);
 
         return {
             add: (x: number, y: number, z: number, group: number) => {
                 ChunkedArray.add3(vertices, x, y, z);
                 ChunkedArray.add(groups, group);
             },
-            getPoint: () => {
+            getPoints: () => {
                 const vb = ChunkedArray.compact(vertices, true) as Float32Array
                 const gb = ChunkedArray.compact(groups, true) as Float32Array
                 return {
-                    kind: 'point',
-                    vertexCount: vertices.elementCount,
-                    vertexBuffer: point ? ValueCell.update(point.vertexBuffer, vb) : ValueCell.create(vb),
-                    groupBuffer: point ? ValueCell.update(point.groupBuffer, gb) : ValueCell.create(gb),
+                    kind: 'points',
+                    pointCount: vertices.elementCount,
+                    centerBuffer: points ? ValueCell.update(points.centerBuffer, vb) : ValueCell.create(vb),
+                    groupBuffer: points ? ValueCell.update(points.groupBuffer, gb) : ValueCell.create(gb),
                 }
             }
         }

+ 24 - 24
src/mol-geo/geometry/point/point.ts → src/mol-geo/geometry/points/points.ts

@@ -8,7 +8,7 @@ import { ValueCell } from 'mol-util'
 import { Mat4 } from 'mol-math/linear-algebra'
 import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
 import { Geometry } from '../geometry';
-import { PointValues } from 'mol-gl/renderable';
+import { PointsValues } from 'mol-gl/renderable';
 import { RuntimeContext } from 'mol-task';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
@@ -18,36 +18,36 @@ import { LocationIterator } from '../../util/location-iterator';
 import { SizeThemeProps } from 'mol-view/theme/size';
 
 /** Point cloud */
-export interface Point {
-    readonly kind: 'point',
+export interface Points {
+    readonly kind: 'points',
     /** Number of vertices in the point cloud */
-    vertexCount: number,
+    pointCount: number,
     /** Vertex buffer as array of xyz values wrapped in a value cell */
-    readonly vertexBuffer: ValueCell<Float32Array>,
+    readonly centerBuffer: ValueCell<Float32Array>,
     /** Group buffer as array of group ids for each vertex wrapped in a value cell */
     readonly groupBuffer: ValueCell<Float32Array>,
 }
 
-export namespace Point {
-    export function createEmpty(point?: Point): Point {
-        const vb = point ? point.vertexBuffer.ref.value : new Float32Array(0)
-        const gb = point ? point.groupBuffer.ref.value : new Float32Array(0)
+export namespace Points {
+    export function createEmpty(points?: Points): Points {
+        const cb = points ? points.centerBuffer.ref.value : new Float32Array(0)
+        const gb = points ? points.groupBuffer.ref.value : new Float32Array(0)
         return {
-            kind: 'point',
-            vertexCount: 0,
-            vertexBuffer: point ? ValueCell.update(point.vertexBuffer, vb) : ValueCell.create(vb),
-            groupBuffer: point ? ValueCell.update(point.groupBuffer, gb) : ValueCell.create(gb),
+            kind: 'points',
+            pointCount: 0,
+            centerBuffer: points ? ValueCell.update(points.centerBuffer, cb) : ValueCell.create(cb),
+            groupBuffer: points ? ValueCell.update(points.groupBuffer, gb) : ValueCell.create(gb),
         }
     }
 
-    export function transformImmediate(point: Point, t: Mat4) {
-        transformRangeImmediate(point, t, 0, point.vertexCount)
+    export function transformImmediate(points: Points, t: Mat4) {
+        transformRangeImmediate(points, t, 0, points.pointCount)
     }
 
-    export function transformRangeImmediate(point: Point, t: Mat4, offset: number, count: number) {
-        const v = point.vertexBuffer.ref.value
-        transformPositionArray(t, v, offset, count)
-        ValueCell.update(point.vertexBuffer, v);
+    export function transformRangeImmediate(points: Points, t: Mat4, offset: number, count: number) {
+        const c = points.centerBuffer.ref.value
+        transformPositionArray(t, c, offset, count)
+        ValueCell.update(points.centerBuffer, c);
     }
 
     //
@@ -61,17 +61,17 @@ export namespace Point {
     }
     export type Props = typeof DefaultProps
 
-    export async function createValues(ctx: RuntimeContext, point: Point, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<PointValues> {
+    export async function createValues(ctx: RuntimeContext, points: Points, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<PointsValues> {
         const { instanceCount, groupCount } = locationIt
         const color = await createColors(ctx, locationIt, props.colorTheme)
         const size = await createSizes(ctx, locationIt, props.sizeTheme)
         const marker = createMarkers(instanceCount * groupCount)
 
-        const counts = { drawCount: point.vertexCount, groupCount, instanceCount }
+        const counts = { drawCount: points.pointCount, groupCount, instanceCount }
 
         return {
-            aPosition: point.vertexBuffer,
-            aGroup: point.groupBuffer,
+            aPosition: points.centerBuffer,
+            aGroup: points.groupBuffer,
             ...color,
             ...size,
             ...marker,
@@ -84,7 +84,7 @@ export namespace Point {
         }
     }
 
-    export function updateValues(values: PointValues, props: Props) {
+    export function updateValues(values: PointsValues, props: Props) {
         Geometry.updateValues(values, props)
         ValueCell.updateIfChanged(values.dPointSizeAttenuation, props.pointSizeAttenuation)
         ValueCell.updateIfChanged(values.dPointFilledCircle, props.pointFilledCircle)

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

@@ -11,7 +11,8 @@ import { SizeThemeProps } from 'mol-view/theme/size';
 import { Representation, RepresentationProps } from '..';
 import { Geometry } from '../../geometry/geometry';
 import { Mesh } from '../../geometry/mesh/mesh';
-import { Point } from '../../geometry/point/point';
+import { Points } from '../../geometry/points/points';
+import { Lines } from '../../geometry/lines/lines';
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
@@ -28,11 +29,17 @@ export const DefaultStructureMeshProps = {
 }
 export type StructureMeshProps = typeof DefaultStructureMeshProps
 
-export const DefaultStructurePointProps = {
-    ...Point.DefaultProps,
+export const DefaultStructurePointsProps = {
+    ...Points.DefaultProps,
     ...DefaultStructureProps,
 }
-export type StructurePointProps = typeof DefaultStructurePointProps
+export type StructurePointsProps = typeof DefaultStructurePointsProps
+
+export const DefaultStructureLinesProps = {
+    ...Lines.DefaultProps,
+    ...DefaultStructureProps,
+}
+export type StructureLinesProps = typeof DefaultStructureLinesProps
 
 export interface VisualUpdateState {
     updateTransform: boolean

+ 10 - 5
src/mol-geo/representation/structure/representation/surface.ts

@@ -13,10 +13,12 @@ import { Loci } from 'mol-model/loci';
 import { PickingId } from '../../../geometry/picking';
 import { Task } from 'mol-task';
 import { GaussianDensityPointVisual, DefaultGaussianDensityPointProps } from '../visual/gaussian-density-point';
+import { DefaultGaussianWireframeProps, GaussianWireframeVisual } from '../visual/gaussian-surface-wireframe';
 
 export const DefaultSurfaceProps = {
-    ...DefaultGaussianSurfaceProps,
-    ...DefaultGaussianDensityPointProps,
+    // ...DefaultGaussianSurfaceProps,
+    // ...DefaultGaussianDensityPointProps,
+    ...DefaultGaussianWireframeProps,
 }
 export type SurfaceProps = typeof DefaultSurfaceProps
 
@@ -26,19 +28,21 @@ export function SurfaceRepresentation(): SurfaceRepresentation {
     let currentProps: SurfaceProps
     const gaussianSurfaceRepr = UnitsRepresentation('Gaussian surface', GaussianSurfaceVisual)
     const gaussianPointRepr = UnitsRepresentation('Gaussian point grid', GaussianDensityPointVisual)
+    const gaussianWireframeRepr = UnitsRepresentation('Gaussian wireframe', GaussianWireframeVisual)
     return {
         label: 'Surface',
         get renderObjects() {
-            return [ ...gaussianSurfaceRepr.renderObjects, ...gaussianPointRepr.renderObjects ]
+            return [ ...gaussianSurfaceRepr.renderObjects, ...gaussianPointRepr.renderObjects, ...gaussianWireframeRepr.renderObjects ]
         },
         get props() {
-            return { ...gaussianSurfaceRepr.props, ...gaussianPointRepr.props }
+            return { ...gaussianSurfaceRepr.props, ...gaussianPointRepr.props, ...gaussianWireframeRepr.props }
         },
         createOrUpdate: (props: Partial<SurfaceProps> = {}, structure?: Structure) => {
             currentProps = Object.assign({}, DefaultSurfaceProps, currentProps, props)
             return Task.create('Creating SurfaceRepresentation', async ctx => {
-                await gaussianSurfaceRepr.createOrUpdate(currentProps, structure).runInContext(ctx)
+                // await gaussianSurfaceRepr.createOrUpdate(currentProps, structure).runInContext(ctx)
                 // await gaussianPointRepr.createOrUpdate(currentProps, structure).runInContext(ctx)
+                await gaussianWireframeRepr.createOrUpdate(currentProps, structure).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {
@@ -50,6 +54,7 @@ export function SurfaceRepresentation(): SurfaceRepresentation {
         destroy() {
             gaussianSurfaceRepr.destroy()
             gaussianPointRepr.destroy()
+            gaussianWireframeRepr.destroy()
         }
     }
 }

+ 189 - 29
src/mol-geo/representation/structure/units-visual.ts

@@ -4,28 +4,40 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+// TODO refactor to make DRY
+
 import { Unit, Structure } from 'mol-model/structure';
 import { RepresentationProps, Visual } from '../';
-import { DefaultStructureMeshProps, VisualUpdateState, DefaultStructurePointProps } from '.';
+import { DefaultStructureMeshProps, VisualUpdateState, DefaultStructurePointsProps, DefaultStructureLinesProps } from '.';
 import { RuntimeContext } from 'mol-task';
 import { PickingId } from '../../geometry/picking';
 import { LocationIterator } from '../../util/location-iterator';
 import { Mesh } from '../../geometry/mesh/mesh';
 import { MarkerAction, applyMarkerAction, createMarkers } from '../../geometry/marker-data';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
-import { MeshRenderObject, PointRenderObject } from 'mol-gl/render-object';
-import { createUnitsMeshRenderObject, createUnitsPointRenderObject, createUnitsTransform } from './visual/util/common';
+import { MeshRenderObject, PointsRenderObject, LinesRenderObject } from 'mol-gl/render-object';
+import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject } from './visual/util/common';
 import { deepEqual, ValueCell, UUID } from 'mol-util';
 import { Interval } from 'mol-data/int';
-import { Point } from '../../geometry/point/point';
+import { Points } from '../../geometry/points/points';
 import { updateRenderableState } from '../../geometry/geometry';
 import { createColors } from '../../geometry/color-data';
 import { createSizes } from '../../geometry/size-data';
+import { Lines } from '../../geometry/lines/lines';
 
 export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 
 export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
 
+function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
+    return (
+        groupA.units.length === groupB.units.length &&
+        Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
+    )
+}
+
+// mesh
+
 export const DefaultUnitsMeshProps = {
     ...DefaultStructureMeshProps,
     unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
@@ -175,37 +187,185 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu
     }
 }
 
-function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
-    return (
-        groupA.units.length === groupB.units.length &&
-        Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
-    )
+// points
+
+export const DefaultUnitsPointsProps = {
+    ...DefaultStructurePointsProps,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type UnitsPointsProps = typeof DefaultUnitsPointsProps
+
+export interface UnitsPointVisualBuilder<P extends UnitsPointsProps> {
+    defaultProps: P
+    createPoints(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, points?: Points): Promise<Points>
+    createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
+    getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
+    mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
+    setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
+}
+
+export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
+    const { defaultProps, createPoints, createLocationIterator, getLoci, mark, setUpdateState } = builder
+    const updateState = VisualUpdateState.create()
+
+    let renderObject: PointsRenderObject | undefined
+    let currentProps: P
+    let points: Points
+    let currentGroup: Unit.SymmetryGroup
+    let currentStructure: Structure
+    let locationIt: LocationIterator
+    let currentConformationId: UUID
+
+    async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
+        currentProps = Object.assign({}, defaultProps, props)
+        currentProps.colorTheme.structure = currentStructure
+        currentGroup = group
+
+        const unit = group.units[0]
+        currentConformationId = Unit.conformationId(unit)
+        points = currentProps.unitKinds.includes(unit.kind)
+            ? await createPoints(ctx, unit, currentStructure, currentProps, points)
+            : Points.createEmpty(points)
+
+        // TODO create empty location iterator when not in unitKinds
+        locationIt = createLocationIterator(group)
+        renderObject = await createUnitsPointsRenderObject(ctx, group, points, locationIt, currentProps)
+    }
+
+    async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
+        if (!renderObject) return
+
+        const newProps = Object.assign({}, currentProps, props)
+        newProps.colorTheme.structure = currentStructure
+        const unit = currentGroup.units[0]
+
+        locationIt.reset()
+        VisualUpdateState.reset(updateState)
+        setUpdateState(updateState, newProps, currentProps)
+
+        const newConformationId = Unit.conformationId(unit)
+        if (newConformationId !== currentConformationId) {
+            currentConformationId = newConformationId
+            updateState.createGeometry = true
+        }
+
+        if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
+
+        if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.updateSize = true
+        if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true
+        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
+
+        //
+
+        if (updateState.updateTransform) {
+            locationIt = createLocationIterator(currentGroup)
+            const { instanceCount, groupCount } = locationIt
+            createUnitsTransform(currentGroup, renderObject.values)
+            createMarkers(instanceCount * groupCount, renderObject.values)
+            updateState.updateColor = true
+        }
+
+        if (updateState.createGeometry) {
+            points = newProps.unitKinds.includes(unit.kind)
+                ? await createPoints(ctx, unit, currentStructure, newProps, points)
+                : Points.createEmpty(points)
+            ValueCell.update(renderObject.values.drawCount, points.pointCount)
+            updateState.updateColor = true
+        }
+
+        if (updateState.updateSize) {
+            await createSizes(ctx, locationIt, newProps.sizeTheme, renderObject.values)
+        }
+
+        if (updateState.updateColor) {
+            await createColors(ctx, locationIt, newProps.colorTheme, renderObject.values)
+        }
+
+        // TODO why do I need to cast here?
+        Points.updateValues(renderObject.values, newProps as UnitsPointsProps)
+        updateRenderableState(renderObject.state, newProps as UnitsPointsProps)
+
+        currentProps = newProps
+    }
+
+    return {
+        get renderObject () { return renderObject },
+        async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
+            if (structureGroup) currentStructure = structureGroup.structure
+            const group = structureGroup ? structureGroup.group : undefined
+            if (!group && !currentGroup) {
+                throw new Error('missing group')
+            } else if (group && (!currentGroup || !renderObject)) {
+                // console.log('unit-visual first create')
+                await create(ctx, group, props)
+            } else if (group && group.hashCode !== currentGroup.hashCode) {
+                // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
+                await create(ctx, group, props)
+            } else {
+                // console.log('unit-visual update')
+                if (group && !sameGroupConformation(group, currentGroup)) {
+                    // console.log('unit-visual new conformation')
+                    currentGroup = group
+                }
+                await update(ctx, props)
+            }
+        },
+        getLoci(pickingId: PickingId) {
+            return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            if (!renderObject) return false
+            const { tMarker } = renderObject.values
+            const { groupCount, instanceCount } = locationIt
+
+            function apply(interval: Interval) {
+                const start = Interval.start(interval)
+                const end = Interval.end(interval)
+                return applyMarkerAction(tMarker.ref.value.array, start, end, action)
+            }
+
+            let changed = false
+            if (isEveryLoci(loci)) {
+                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
+            } else {
+                changed = mark(loci, currentGroup, apply)
+            }
+            if (changed) {
+                ValueCell.update(tMarker, tMarker.ref.value)
+            }
+            return changed
+        },
+        destroy() {
+            // TODO
+            renderObject = undefined
+        }
+    }
 }
 
-//
+// lines
 
-export const DefaultUnitsPointProps = {
-    ...DefaultStructurePointProps,
+export const DefaultUnitsLinesProps = {
+    ...DefaultStructureLinesProps,
     unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
 }
-export type UnitsPointProps = typeof DefaultUnitsPointProps
+export type UnitsLinesProps = typeof DefaultUnitsLinesProps
 
-export interface UnitsPointVisualBuilder<P extends UnitsPointProps> {
+export interface UnitsLinesVisualBuilder<P extends UnitsLinesProps> {
     defaultProps: P
-    createPoint(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, point?: Point): Promise<Point>
+    createLines(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, lines?: Lines): Promise<Lines>
     createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
     getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
     mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
     setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
 }
 
-export function UnitsPointVisual<P extends UnitsPointProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
-    const { defaultProps, createPoint, createLocationIterator, getLoci, mark, setUpdateState } = builder
+export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> {
+    const { defaultProps, createLines, createLocationIterator, getLoci, mark, setUpdateState } = builder
     const updateState = VisualUpdateState.create()
 
-    let renderObject: PointRenderObject | undefined
+    let renderObject: LinesRenderObject | undefined
     let currentProps: P
-    let point: Point
+    let lines: Lines
     let currentGroup: Unit.SymmetryGroup
     let currentStructure: Structure
     let locationIt: LocationIterator
@@ -218,13 +378,13 @@ export function UnitsPointVisual<P extends UnitsPointProps>(builder: UnitsPointV
 
         const unit = group.units[0]
         currentConformationId = Unit.conformationId(unit)
-        point = currentProps.unitKinds.includes(unit.kind)
-            ? await createPoint(ctx, unit, currentStructure, currentProps, point)
-            : Point.createEmpty(point)
+        lines = currentProps.unitKinds.includes(unit.kind)
+            ? await createLines(ctx, unit, currentStructure, currentProps, lines)
+            : Lines.createEmpty(lines)
 
         // TODO create empty location iterator when not in unitKinds
         locationIt = createLocationIterator(group)
-        renderObject = await createUnitsPointRenderObject(ctx, group, point, locationIt, currentProps)
+        renderObject = await createUnitsLinesRenderObject(ctx, group, lines, locationIt, currentProps)
     }
 
     async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
@@ -261,10 +421,10 @@ export function UnitsPointVisual<P extends UnitsPointProps>(builder: UnitsPointV
         }
 
         if (updateState.createGeometry) {
-            point = newProps.unitKinds.includes(unit.kind)
-                ? await createPoint(ctx, unit, currentStructure, newProps, point)
-                : Point.createEmpty(point)
-            ValueCell.update(renderObject.values.drawCount, point.vertexCount)
+            lines = newProps.unitKinds.includes(unit.kind)
+                ? await createLines(ctx, unit, currentStructure, newProps, lines)
+                : Lines.createEmpty(lines)
+            ValueCell.update(renderObject.values.drawCount, lines.lineCount)
             updateState.updateColor = true
         }
 
@@ -277,8 +437,8 @@ export function UnitsPointVisual<P extends UnitsPointProps>(builder: UnitsPointV
         }
 
         // TODO why do I need to cast here?
-        Point.updateValues(renderObject.values, newProps as UnitsPointProps)
-        updateRenderableState(renderObject.state, newProps as UnitsPointProps)
+        Lines.updateValues(renderObject.values, newProps as UnitsLinesProps)
+        updateRenderableState(renderObject.state, newProps as UnitsLinesProps)
 
         currentProps = newProps
     }

+ 9 - 9
src/mol-geo/representation/structure/visual/element-point.ts

@@ -10,12 +10,12 @@ import { UnitsVisual, VisualUpdateState } from '..';
 import { getElementLoci, StructureElementIterator, markElement } from './util/element';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { SizeThemeProps } from 'mol-view/theme/size';
-import { UnitsPointVisual, DefaultUnitsPointProps } from '../units-visual';
-import { Point } from '../../../geometry/point/point';
-import { PointBuilder } from '../../../geometry/point/point-builder';
+import { UnitsPointsVisual, DefaultUnitsPointsProps } from '../units-visual';
+import { Points } from '../../../geometry/points/points';
+import { PointsBuilder } from '../../../geometry/points/points-builder';
 
 export const DefaultElementPointProps = {
-    ...DefaultUnitsPointProps,
+    ...DefaultUnitsPointsProps,
 
     sizeTheme: { name: 'uniform', value: 0.2 } as SizeThemeProps,
     pointSizeAttenuation: true,
@@ -24,10 +24,10 @@ export type ElementPointProps = typeof DefaultElementPointProps
 
 // TODO size
 
-export async function createElementPoint(ctx: RuntimeContext, unit: Unit, structure: Structure, props: ElementPointProps, point: Point) {
+export async function createElementPoint(ctx: RuntimeContext, unit: Unit, structure: Structure, props: ElementPointProps, points: Points) {
     const elements = unit.elements
     const n = elements.length
-    const builder = PointBuilder.create(n, n / 10, point)
+    const builder = PointsBuilder.create(n, n / 10, points)
 
     const pos = unit.conformation.invariantPosition
     const p = Vec3.zero()
@@ -40,13 +40,13 @@ export async function createElementPoint(ctx: RuntimeContext, unit: Unit, struct
             await ctx.update({ message: 'Creating points', current: i, max: n });
         }
     }
-    return builder.getPoint()
+    return builder.getPoints()
 }
 
 export function ElementPointVisual(): UnitsVisual<ElementPointProps> {
-    return UnitsPointVisual<ElementPointProps>({
+    return UnitsPointsVisual<ElementPointProps>({
         defaultProps: DefaultElementPointProps,
-        createPoint: createElementPoint,
+        createPoints: createElementPoint,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,

+ 9 - 9
src/mol-geo/representation/structure/visual/gaussian-density-point.ts

@@ -10,14 +10,14 @@ import { UnitsVisual, VisualUpdateState } from '..';
 import { StructureElementIterator } from './util/element';
 import { EmptyLoci } from 'mol-model/loci';
 import { Vec3 } from 'mol-math/linear-algebra';
-import { UnitsPointVisual, DefaultUnitsPointProps } from '../units-visual';
+import { UnitsPointsVisual, DefaultUnitsPointsProps } from '../units-visual';
 import { computeGaussianDensity, DefaultGaussianDensityProps } from './util/gaussian';
-import { Point } from '../../../geometry/point/point';
-import { PointBuilder } from '../../../geometry/point/point-builder';
+import { Points } from '../../../geometry/points/points';
+import { PointsBuilder } from '../../../geometry/points/points-builder';
 import { SizeThemeProps } from 'mol-view/theme/size';
 
 export const DefaultGaussianDensityPointProps = {
-    ...DefaultUnitsPointProps,
+    ...DefaultUnitsPointsProps,
     ...DefaultGaussianDensityProps,
 
     sizeTheme: { name: 'uniform', value: 1 } as SizeThemeProps,
@@ -25,14 +25,14 @@ export const DefaultGaussianDensityPointProps = {
 }
 export type GaussianDensityPointProps = typeof DefaultGaussianDensityPointProps
 
-export async function createGaussianDensityPoint(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityPointProps, point?: Point) {
+export async function createGaussianDensityPoint(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityPointProps, points?: Points) {
     const { transform, field: { space, data } } = await computeGaussianDensity(unit, structure, props).runAsChild(ctx)
 
     const { dimensions, get } = space
     const [ xn, yn, zn ] = dimensions
 
     const n = xn * yn * zn * 3
-    const builder = PointBuilder.create(n, n / 10, point)
+    const builder = PointsBuilder.create(n, n / 10, points)
 
     const p = Vec3.zero()
     let i = 0
@@ -52,13 +52,13 @@ export async function createGaussianDensityPoint(ctx: RuntimeContext, unit: Unit
             }
         }
     }
-    return builder.getPoint()
+    return builder.getPoints()
 }
 
 export function GaussianDensityPointVisual(): UnitsVisual<GaussianDensityPointProps> {
-    return UnitsPointVisual<GaussianDensityPointProps>({
+    return UnitsPointsVisual<GaussianDensityPointProps>({
         defaultProps: DefaultGaussianDensityPointProps,
-        createPoint: createGaussianDensityPoint,
+        createPoints: createGaussianDensityPoint,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: () => EmptyLoci,
         mark: () => false,

+ 71 - 0
src/mol-geo/representation/structure/visual/gaussian-surface-wireframe.ts

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Unit, Structure } from 'mol-model/structure';
+import { UnitsVisual, VisualUpdateState } from '..';
+import { RuntimeContext } from 'mol-task'
+import { Mesh } from '../../../geometry/mesh/mesh';
+import { UnitsLinesVisual, DefaultUnitsLinesProps } from '../units-visual';
+import { StructureElementIterator, getElementLoci, markElement } from './util/element';
+import { computeMarchingCubes } from '../../../util/marching-cubes/algorithm';
+import { computeGaussianDensity, DefaultGaussianDensityProps, GaussianDensityProps } from './util/gaussian';
+import { Lines } from '../../../geometry/lines/lines';
+import { LinesBuilder } from '../../../geometry/lines/lines-builder';
+
+async function createGaussianWireframe(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
+    const { smoothness } = props
+    const { transform, field, idField } = await computeGaussianDensity(unit, structure, props).runAsChild(ctx)
+
+    const surface = await computeMarchingCubes({
+        isoLevel: Math.exp(-smoothness),
+        scalarField: field,
+        idField
+    }).runAsChild(ctx)
+
+    Mesh.transformImmediate(surface, transform)
+
+    const vb = surface.vertexBuffer.ref.value
+    const ib = surface.indexBuffer.ref.value
+    const gb = surface.groupBuffer.ref.value
+
+    const builder = LinesBuilder.create(surface.triangleCount * 3, surface.triangleCount / 10, lines)
+
+    // TODO avoid duplicate lines and move to '../../../geometry/lines/lines' as Lines.fromMesh()
+    for (let i = 0, il = surface.triangleCount * 3; i < il; i += 3) {
+        const i0 = ib[i], i1 = ib[i + 1], i2 = ib[i + 2];
+        const x0 = vb[i0 * 3], y0 = vb[i0 * 3 + 1], z0 = vb[i0 * 3 + 2];
+        const x1 = vb[i1 * 3], y1 = vb[i1 * 3 + 1], z1 = vb[i1 * 3 + 2];
+        const x2 = vb[i2 * 3], y2 = vb[i2 * 3 + 1], z2 = vb[i2 * 3 + 2];
+        builder.add(x0, y0, z0, x1, y1, z1, gb[i0])
+        builder.add(x0, y0, z0, x2, y2, z2, gb[i0])
+        builder.add(x1, y1, z1, x2, y2, z2, gb[i1])
+    }
+
+    const l = builder.getLines();
+    console.log(l)
+    return l
+}
+
+export const DefaultGaussianWireframeProps = {
+    ...DefaultUnitsLinesProps,
+    ...DefaultGaussianDensityProps,
+}
+export type GaussianWireframeProps = typeof DefaultGaussianWireframeProps
+
+export function GaussianWireframeVisual(): UnitsVisual<GaussianWireframeProps> {
+    return UnitsLinesVisual<GaussianWireframeProps>({
+        defaultProps: DefaultGaussianWireframeProps,
+        createLines: createGaussianWireframe,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement,
+        setUpdateState: (state: VisualUpdateState, newProps: GaussianWireframeProps, currentProps: GaussianWireframeProps) => {
+            if (newProps.resolutionFactor !== currentProps.resolutionFactor) state.createGeometry = true
+            if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
+            if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true
+        }
+    })
+}

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

@@ -8,13 +8,13 @@ import { Unit, Structure } from 'mol-model/structure';
 import { LocationIterator } from '../../../../util/location-iterator';
 import { Mesh } from '../../../../geometry/mesh/mesh';
 import { StructureProps } from '../..';
-import { createMeshRenderObject, createPointRenderObject } from 'mol-gl/render-object';
+import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject } from 'mol-gl/render-object';
 import { RuntimeContext } from 'mol-task';
-import { PointProps } from 'mol-geo/representation/structure/representation/point';
 import { TransformData, createIdentityTransform, createTransform } from '../../../../geometry/transform-data';
-import { Point } from '../../../../geometry/point/point';
+import { Points } from '../../../../geometry/points/points';
 import { createRenderableState } from '../../../../geometry/geometry';
 import { Mat4 } from 'mol-math/linear-algebra';
+import { Lines } from '../../../../geometry/lines/lines';
 
 export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) {
     const unitCount = units.length
@@ -44,13 +44,25 @@ export async function createUnitsMeshRenderObject(ctx: RuntimeContext, group: Un
     return createMeshRenderObject(values, state)
 }
 
-// point
+// points
 
-type StructurePointProps = PointProps & StructureProps
+type StructurePointsProps = Points.Props & StructureProps
 
-export async function createUnitsPointRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, point: Point, locationIt: LocationIterator, props: StructurePointProps) {
+export async function createUnitsPointsRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, points: Points, locationIt: LocationIterator, props: StructurePointsProps) {
     const transform = createUnitsTransform(group)
-    const values = await Point.createValues(ctx, point, transform, locationIt, props)
+    const values = await Points.createValues(ctx, points, transform, locationIt, props)
     const state = createRenderableState(props)
-    return createPointRenderObject(values, state)
+    return createPointsRenderObject(values, state)
+}
+
+// lines
+
+type StructureLinesProps = Lines.Props & StructureProps
+
+export async function createUnitsLinesRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, lines: Lines, locationIt: LocationIterator, props: StructureLinesProps) {
+    const transform = createUnitsTransform(group)
+    const values = await Lines.createValues(ctx, lines, transform, locationIt, props)
+    console.log('values', values)
+    const state = createRenderableState(props)
+    return createLinesRenderObject(values, state)
 }

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

@@ -11,7 +11,7 @@ import { Box3D } from 'mol-math/geometry';
 import { SizeTheme } from 'mol-view/theme/size';
 
 export const DefaultGaussianDensityProps = {
-    resolutionFactor: 6,
+    resolutionFactor: 4,
     radiusOffset: 0,
     smoothness: 1.5,
 }

+ 4 - 4
src/mol-gl/_spec/renderer.spec.ts

@@ -15,8 +15,8 @@ import { createValueColor } from 'mol-geo/geometry/color-data';
 import { createValueSize } from 'mol-geo/geometry/size-data';
 import { createContext } from '../webgl/context';
 import { RenderableState } from '../renderable';
-import { createPointRenderObject } from '../render-object';
-import { PointValues } from '../renderable/point';
+import { createPointsRenderObject } from '../render-object';
+import { PointsValues } from '../renderable/points';
 import Scene from '../scene';
 import { createEmptyMarkers } from 'mol-geo/geometry/marker-data';
 import { fillSerial } from 'mol-util/array';
@@ -56,7 +56,7 @@ function createPoints() {
     const m4 = Mat4.identity()
     Mat4.toArray(m4, aTransform.ref.value, 0)
 
-    const values: PointValues = {
+    const values: PointsValues = {
         aPosition,
         aGroup,
         aTransform,
@@ -82,7 +82,7 @@ function createPoints() {
         depthMask: true,
     }
 
-    return createPointRenderObject(values, state)
+    return createPointsRenderObject(values, state)
 }
 
 describe('renderer', () => {

+ 11 - 8
src/mol-gl/render-object.ts

@@ -4,30 +4,33 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { PointRenderable, MeshRenderable, RenderableState } from './renderable'
+import { PointsRenderable, MeshRenderable, RenderableState, MeshValues, PointsValues, LinesValues, LinesRenderable } from './renderable'
 import { RenderableValues } from './renderable/schema';
 import { idFactory } from 'mol-util/id-factory';
 import { Context } from './webgl/context';
-import { MeshValues } from './renderable/mesh';
-import { PointValues } from './renderable/point';
 
 const getNextId = idFactory(0, 0x7FFFFFFF)
 
 export interface BaseRenderObject { id: number, type: string, values: RenderableValues, state: RenderableState }
 export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', values: MeshValues }
-export interface PointRenderObject extends BaseRenderObject { type: 'point', values: PointValues }
-export type RenderObject = MeshRenderObject | PointRenderObject
+export interface PointsRenderObject extends BaseRenderObject { type: 'points', values: PointsValues }
+export interface LinesRenderObject extends BaseRenderObject { type: 'lines', values: LinesValues }
+export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject
 
 export function createMeshRenderObject(values: MeshValues, state: RenderableState): MeshRenderObject {
     return { id: getNextId(), type: 'mesh', values, state }
 }
-export function createPointRenderObject(values: PointValues, state: RenderableState): PointRenderObject {
-    return { id: getNextId(), type: 'point', values, state }
+export function createPointsRenderObject(values: PointsValues, state: RenderableState): PointsRenderObject {
+    return { id: getNextId(), type: 'points', values, state }
+}
+export function createLinesRenderObject(values: LinesValues, state: RenderableState): LinesRenderObject {
+    return { id: getNextId(), type: 'lines', values, state }
 }
 
 export function createRenderable(ctx: Context, o: RenderObject) {
     switch (o.type) {
         case 'mesh': return MeshRenderable(ctx, o.id, o.values, o.state)
-        case 'point': return PointRenderable(ctx, o.id, o.values, o.state)
+        case 'points': return PointsRenderable(ctx, o.id, o.values, o.state)
+        case 'lines': return LinesRenderable(ctx, o.id, o.values, o.state)
     }
 }

+ 16 - 8
src/mol-gl/renderable.ts

@@ -8,7 +8,9 @@ import { Program } from './webgl/program';
 import { RenderableValues, Values, RenderableSchema, BaseValues } from './renderable/schema';
 import { RenderVariant, RenderItem } from './webgl/render-item';
 import { Sphere3D } from 'mol-math/geometry';
-import { calculateBoundingSphereFromValues } from './renderable/util';
+// import { calculateBoundingSphereFromValues } from './renderable/util';
+// import { Sphere } from 'mol-geo/primitive/sphere';
+import { Vec3 } from 'mol-math/linear-algebra';
 
 export type RenderableState = {
     visible: boolean
@@ -28,27 +30,33 @@ export interface Renderable<T extends RenderableValues & BaseValues> {
 }
 
 export function createRenderable<T extends Values<RenderableSchema> & BaseValues>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> {
-    let boundingSphere: Sphere3D | undefined
+    // TODO
+    let boundingSphere: Sphere3D = Sphere3D.create(Vec3.zero(), 50)
 
     return {
         get values () { return values },
         get state () { return state },
         get boundingSphere () {
-            if (boundingSphere) return boundingSphere
-            boundingSphere = calculateBoundingSphereFromValues(values)
             return boundingSphere
+            // TODO
+            // if (boundingSphere) return boundingSphere
+            // boundingSphere = calculateBoundingSphereFromValues(values)
+            // return boundingSphere
         },
         get opaque () { return values.uAlpha.ref.value === 1 },
 
         render: (variant: RenderVariant) => renderItem.render(variant),
         getProgram: (variant: RenderVariant) => renderItem.getProgram(variant),
         update: () => {
-            const valueChanges = renderItem.update()
-            if (valueChanges.attributes) boundingSphere = undefined
+            renderItem.update()
+            // TODO
+            // const valueChanges = renderItem.update()
+            // if (valueChanges.attributes) boundingSphere = undefined
         },
         dispose: () => renderItem.destroy()
     }
 }
 
-export { PointRenderable, PointSchema, PointValues } from './renderable/point'
-export { MeshRenderable, MeshSchema, MeshValues } from './renderable/mesh'
+export { MeshRenderable, MeshSchema, MeshValues } from './renderable/mesh'
+export { PointsRenderable, PointsSchema, PointsValues } from './renderable/points'
+export { LinesRenderable, LinesSchema, LinesValues } from './renderable/lines'

+ 0 - 5
src/mol-gl/renderable/line.ts

@@ -1,5 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */

+ 37 - 0
src/mol-gl/renderable/lines.ts

@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Renderable, RenderableState, createRenderable } from '../renderable'
+import { Context } from '../webgl/context';
+import { createRenderItem } from '../webgl/render-item';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, DefineSpec, Values, InternalSchema, SizeSchema, ElementsSpec } from './schema';
+import { ValueCell } from 'mol-util';
+import { LinesShaderCode } from '../shader-code';
+
+export const LinesSchema = {
+    ...BaseSchema,
+    ...SizeSchema,
+    aMapping: AttributeSpec('float32', 2, 0),
+    aStart: AttributeSpec('float32', 3, 0),
+    aEnd: AttributeSpec('float32', 3, 0),
+    elements: ElementsSpec('uint32'),
+    dLineSizeAttenuation: DefineSpec('boolean'),
+    dDoubleSided: DefineSpec('boolean'),
+    dFlipSided: DefineSpec('boolean'),
+}
+export type LinesSchema = typeof LinesSchema
+export type LinesValues = Values<LinesSchema>
+
+export function LinesRenderable(ctx: Context, id: number, values: LinesValues, state: RenderableState): Renderable<LinesValues> {
+    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...LinesSchema }
+    const internalValues = {
+        uObjectId: ValueCell.create(id)
+    }
+    const shaderCode = LinesShaderCode
+    const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues })
+
+    return createRenderable(renderItem, values, state);
+}

+ 1 - 0
src/mol-gl/renderable/mesh.ts

@@ -13,6 +13,7 @@ import { ValueCell } from 'mol-util';
 
 export const MeshSchema = {
     ...BaseSchema,
+    aPosition: AttributeSpec('float32', 3, 0),
     aNormal: AttributeSpec('float32', 3, 0),
     elements: ElementsSpec('uint32'),
     dFlatShaded: DefineSpec('boolean'),

+ 10 - 13
src/mol-gl/renderable/point.ts → src/mol-gl/renderable/points.ts

@@ -7,30 +7,27 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable'
 import { Context } from '../webgl/context';
 import { createRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema, TextureSpec } from './schema';
-import { PointShaderCode } from '../shader-code';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema, SizeSchema } from './schema';
+import { PointsShaderCode } from '../shader-code';
 import { ValueCell } from 'mol-util';
 
-export const PointSchema = {
+export const PointsSchema = {
     ...BaseSchema,
-    aSize: AttributeSpec('float32', 1, 0),
-    uSize: UniformSpec('f'),
-    uSizeTexDim: UniformSpec('v2'),
-    tSize: TextureSpec('alpha', 'ubyte'),
-    dSizeType: DefineSpec('string', ['uniform', 'attribute']),
+    ...SizeSchema,
+    aPosition: AttributeSpec('float32', 3, 0),
     dPointSizeAttenuation: DefineSpec('boolean'),
     dPointFilledCircle: DefineSpec('boolean'),
     uPointEdgeBleach: UniformSpec('f'),
 }
-export type PointSchema = typeof PointSchema
-export type PointValues = Values<PointSchema>
+export type PointsSchema = typeof PointsSchema
+export type PointsValues = Values<PointsSchema>
 
-export function PointRenderable(ctx: Context, id: number, values: PointValues, state: RenderableState): Renderable<PointValues> {
-    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointSchema }
+export function PointsRenderable(ctx: Context, id: number, values: PointsValues, state: RenderableState): Renderable<PointsValues> {
+    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointsSchema }
     const internalValues = {
         uObjectId: ValueCell.create(id)
     }
-    const shaderCode = PointShaderCode
+    const shaderCode = PointsShaderCode
     const renderItem = createRenderItem(ctx, 'points', shaderCode, schema, { ...values, ...internalValues })
     const renderable = createRenderable(renderItem, values, state);
 

+ 22 - 6
src/mol-gl/renderable/schema.ts

@@ -141,27 +141,43 @@ export const InternalSchema = {
     uObjectId: UniformSpec('i'),
 }
 
+export const ColorSchema = {
+    aColor: AttributeSpec('float32', 3, 0),
+    uColor: UniformSpec('v3'),
+    uColorTexDim: UniformSpec('v2'),
+    tColor: TextureSpec('rgb', 'ubyte'),
+    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']),
+}
+export type ColorSchema = typeof ColorSchema
+export type ColorValues = Values<ColorSchema>
+
+export const SizeSchema = {
+    aSize: AttributeSpec('float32', 1, 0),
+    uSize: UniformSpec('f'),
+    uSizeTexDim: UniformSpec('v2'),
+    tSize: TextureSpec('alpha', 'ubyte'),
+    dSizeType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']),
+}
+export type SizeSchema = typeof SizeSchema
+export type SizeValues = Values<SizeSchema>
+
 export const BaseSchema = {
+    ...ColorSchema,
+
     aInstance: AttributeSpec('float32', 1, 1),
-    aPosition: AttributeSpec('float32', 3, 0),
     aGroup: AttributeSpec('float32', 1, 0),
     aTransform: AttributeSpec('float32', 16, 1),
-    aColor: AttributeSpec('float32', 3, 0),
 
     uAlpha: UniformSpec('f'),
     uInstanceCount: UniformSpec('i'),
     uGroupCount: UniformSpec('i'),
-    uColor: UniformSpec('v3'),
-    uColorTexDim: UniformSpec('v2'),
     uMarkerTexDim: UniformSpec('v2'),
 
-    tColor: TextureSpec('rgb', 'ubyte'),
     tMarker: TextureSpec('alpha', 'ubyte'),
 
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
 
-    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']),
     dUseFog: DefineSpec('boolean'),
 }
 export type BaseSchema = typeof BaseSchema

+ 8 - 3
src/mol-gl/shader-code.ts

@@ -24,9 +24,14 @@ export function ShaderCode(vert: string, frag: string): ShaderCode {
     return { id: shaderCodeId(), vert, frag }
 }
 
-export const PointShaderCode = ShaderCode(
-    require('mol-gl/shader/point.vert'),
-    require('mol-gl/shader/point.frag')
+export const PointsShaderCode = ShaderCode(
+    require('mol-gl/shader/points.vert'),
+    require('mol-gl/shader/points.frag')
+)
+
+export const LinesShaderCode = ShaderCode(
+    require('mol-gl/shader/lines.vert'),
+    require('mol-gl/shader/lines.frag')
 )
 
 export const MeshShaderCode = ShaderCode(

+ 24 - 0
src/mol-gl/shader/lines.frag

@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+precision highp float;
+precision highp int;
+
+#pragma glslify: import('./chunks/common-frag-params.glsl')
+#pragma glslify: import('./chunks/color-frag-params.glsl')
+
+void main(){
+    #pragma glslify: import('./chunks/assign-material-color.glsl')
+
+    #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
+        gl_FragColor = material;
+    #else
+        gl_FragColor = material;
+
+        #pragma glslify: import('./chunks/apply-marker-color.glsl')
+        #pragma glslify: import('./chunks/apply-fog.glsl')
+    #endif
+}

+ 127 - 0
src/mol-gl/shader/lines.vert

@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ *
+ * heavily based on code by WestLangley from https://github.com/WestLangley/three.js/blob/af28b2fb706ac109771ecad0a7447fad90ab3210/examples/js/lines/LineMaterial.js
+ */
+
+precision highp float;
+precision highp int;
+
+#pragma glslify: import('./chunks/common-vert-params.glsl')
+#pragma glslify: import('./chunks/color-vert-params.glsl')
+
+uniform float uPixelRatio;
+uniform float uViewportHeight;
+
+#if defined(dSizeType_uniform)
+    uniform float uSize;
+#elif defined(dSizeType_attribute)
+    attribute float aSize;
+#elif defined(dSizeType_instance) || defined(dSizeType_group) || defined(dSizeType_groupInstance)
+    varying vec4 vSize;
+    uniform vec2 uSizeTexDim;
+    uniform sampler2D tSize;
+#endif
+
+attribute vec3 aPosition;
+attribute mat4 aTransform;
+attribute float aInstance;
+attribute float aGroup;
+
+attribute vec2 aMapping;
+attribute vec3 aStart;
+attribute vec3 aEnd;
+
+void trimSegment(const in vec4 start, inout vec4 end) {
+    // trim end segment so it terminates between the camera plane and the near plane
+    // conservative estimate of the near plane
+    float a = uProjection[2][2];  // 3nd entry in 3th column
+    float b = uProjection[3][2];  // 3nd entry in 4th column
+    float nearEstimate = -0.5 * b / a;
+    float alpha = (nearEstimate - start.z) / (end.z - start.z);
+    end.xyz = mix(start.xyz, end.xyz, alpha);
+}
+
+void main(){
+    #pragma glslify: import('./chunks/assign-color-varying.glsl')
+
+    // TODO move to chunk (also in point.vert)
+    #if defined(dSizeType_uniform)
+        float size = uSize;
+    #elif defined(dSizeType_attribute)
+        float size = aSize;
+    #elif defined(dSizeType_instance)
+        float size = readFromTexture(tSize, aInstance, uSizeTexDim).r;
+    #elif defined(dSizeType_group)
+        float size = readFromTexture(tSize, aGroup, uSizeTexDim).r;
+    #elif defined(dSizeType_groupInstance)
+        float size = readFromTexture(tSize, aInstance * float(uGroupCount) + aGroup, uSizeTexDim).r;
+    #endif
+
+    float linewidth = 3.0; // size;
+
+    mat4 modelView = uView * uModel * aTransform;
+
+    // camera space
+    vec4 start = modelView * vec4(aStart, 1.0);
+    vec4 end = modelView * vec4(aEnd, 1.0);
+
+    // special case for perspective projection, and segments that terminate either in, or behind, the camera plane
+    // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
+    // but we need to perform ndc-space calculations in the shader, so we must address this issue directly
+    // perhaps there is a more elegant solution -- WestLangley
+    bool perspective = (uProjection[2][3] == -1.0); // 4th entry in the 3rd column
+    if (perspective) {
+        if (start.z < 0.0 && end.z >= 0.0) {
+            trimSegment(start, end);
+        } else if (end.z < 0.0 && start.z >= 0.0) {
+            trimSegment(end, start);
+        }
+    }
+
+    // clip space
+    vec4 clipStart = uProjection * start;
+    vec4 clipEnd = uProjection * end;
+
+    // ndc space
+    vec2 ndcStart = clipStart.xy / clipStart.w;
+    vec2 ndcEnd = clipEnd.xy / clipEnd.w;
+
+    // direction
+    vec2 dir = ndcEnd - ndcStart;
+
+    // account for clip-space aspect ratio
+    dir.x *= uPixelRatio;
+    dir = normalize(dir);
+
+    // perpendicular to dir
+    vec2 offset = vec2(dir.y, - dir.x);
+
+    // undo aspect ratio adjustment
+    dir.x /= uPixelRatio;
+    offset.x /= uPixelRatio;
+
+    // sign flip
+    if (aMapping.x < 0.0) offset *= -1.0;
+
+    // adjust for linewidth
+    offset *= linewidth;
+
+    // adjust for clip-space to screen-space conversion
+    offset /= uViewportHeight;
+
+    // select end
+    vec4 clip = (aMapping.y < 0.5) ? clipStart : clipEnd;
+
+    // back to clip space
+    offset *= clip.w;
+    clip.xy += offset;
+    gl_Position = clip;
+
+    // gl_Position = uProjection * (modelView * vec4(aEnd.x * 5.0 - 5.0, aMapping.y * 5.0, 2.0, 1.0));
+
+    // TODO
+    // vViewPosition = (projectionMatrixInverse * clip).xyz;
+}

+ 0 - 0
src/mol-gl/shader/point.frag → src/mol-gl/shader/points.frag


+ 0 - 0
src/mol-gl/shader/point.vert → src/mol-gl/shader/points.vert