diff --git a/src/apps/render-test/components/detail.tsx b/src/apps/render-test/components/sphere-detail.tsx similarity index 75% rename from src/apps/render-test/components/detail.tsx rename to src/apps/render-test/components/sphere-detail.tsx index 9bbd84a6de3b6f208ece2c5d52f959f463a9b9d9..1b19cca359238d3b5d4a0e99dfb80c7d4ef03c99 100644 --- a/src/apps/render-test/components/detail.tsx +++ b/src/apps/render-test/components/sphere-detail.tsx @@ -14,25 +14,25 @@ import Select from 'material-ui/Select'; import State from '../state' import Observer from './observer'; -interface DetailState { +interface SphereDetailState { loading: boolean value: number } -export default class Detail extends Observer<{ state: State } & WithStyles, DetailState> { - state: DetailState = { loading: false, value: 2 } +export default class SphereDetail extends Observer<{ state: State } & WithStyles, SphereDetailState> { + state: SphereDetailState = { loading: false, value: 2 } componentDidMount() { this.subscribe(this.props.state.loading, value => { this.setState({ loading: value }); }); - this.subscribe(this.props.state.detail, value => { + this.subscribe(this.props.state.sphereDetail, value => { this.setState({ value }); }); } handleValueChange = (event: React.ChangeEvent<any>) => { - this.props.state.detail.next(event.target.value) + this.props.state.sphereDetail.next(event.target.value) } render() { @@ -43,14 +43,14 @@ export default class Detail extends Observer<{ state: State } & WithStyles, Deta }) return <FormControl className={classes.formControl}> - <InputLabel htmlFor='detail-value'>Detail</InputLabel> + <InputLabel htmlFor='sphere-detail-value'>Sphere Detail</InputLabel> <Select className={classes.selectField} value={this.state.value} onChange={this.handleValueChange} inputProps={{ name: 'value', - id: 'detail-value', + id: 'sphere-detail-value', }} > {items} diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts index 74f6cd447869012965e2f13686421b9d660da27c..bb22845aa08610aba040d98747a1638d647c93af 100644 --- a/src/apps/render-test/state.ts +++ b/src/apps/render-test/state.ts @@ -20,9 +20,12 @@ import { Run } from 'mol-task' import { Symmetry, Structure, Model } from 'mol-model/structure' // import mcubes from './utils/mcubes' -import { getModelFromPdbId, getModelFromFile, log } from './utils' +import { getModelFromPdbId, getModelFromFile, log, Volume, getVolumeFromEmdId } from './utils' import { StructureRepresentation } from 'mol-geo/representation/structure'; import { Color } from 'mol-util/color'; +import Surface, { SurfaceProps } from 'mol-geo/representation/volume/surface'; +import { VolumeIsoValue } from 'mol-model/volume'; +import { VolumeRepresentation } from 'mol-geo/representation/volume'; // import Cylinder from 'mol-geo/primitive/cylinder'; @@ -37,14 +40,16 @@ export type ColorTheme = keyof typeof ColorTheme export default class State { viewer: Viewer - pdbId = '4cup' + pdbId = '' + emdId = '8689' model = new BehaviorSubject<Model | undefined>(undefined) + volume = new BehaviorSubject<Volume | undefined>(undefined) initialized = new BehaviorSubject<boolean>(false) loading = new BehaviorSubject<boolean>(false) colorTheme = new BehaviorSubject<ColorTheme>('element-symbol') colorValue = new BehaviorSubject<Color>(0xFF4411) - detail = new BehaviorSubject<number>(0) + sphereDetail = new BehaviorSubject<number>(0) assembly = new BehaviorSubject<string>('') pointVisibility = new BehaviorSubject<boolean>(true) @@ -52,11 +57,12 @@ export default class State { pointRepr: StructureRepresentation<PointProps> spacefillRepr: StructureRepresentation<SpacefillProps> + surfaceRepr: VolumeRepresentation<SurfaceProps> constructor() { this.colorTheme.subscribe(() => this.update()) this.colorValue.subscribe(() => this.update()) - this.detail.subscribe(() => this.update()) + this.sphereDetail.subscribe(() => this.update()) this.assembly.subscribe(() => this.initStructure()) this.pointVisibility.subscribe(() => this.updateVisibility()) @@ -66,7 +72,7 @@ export default class State { getSpacefillProps (): SpacefillProps { const colorThemeName = this.colorTheme.getValue() return { - detail: this.detail.getValue(), + detail: this.sphereDetail.getValue(), colorTheme: colorThemeName === 'uniform' ? { name: colorThemeName, value: this.colorValue.getValue() } : { name: colorThemeName } @@ -87,6 +93,7 @@ export default class State { this.viewer = Viewer.create(canvas, container) this.initialized.next(true) this.loadPdbId() + this.loadEmdId() this.viewer.animate() } @@ -97,7 +104,7 @@ export default class State { let structure: Structure const assemblies = model.symmetry.assemblies if (assemblies.length) { - structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), log, 100) + structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), log, 500) } else { structure = Structure.ofModel(model) } @@ -105,8 +112,8 @@ export default class State { } async initStructure () { - const { viewer, model } = this - if (!viewer || !model) return + const { viewer } = this + if (!viewer || !this.model.getValue()) return viewer.clear() @@ -114,11 +121,11 @@ export default class State { if (!structure) return this.pointRepr = StructureRepresentation(Point) - await Run(this.pointRepr.create(structure, this.getPointProps()), log, 100) + await Run(this.pointRepr.create(structure, this.getPointProps()), log, 500) viewer.add(this.pointRepr) this.spacefillRepr = StructureRepresentation(Spacefill) - await Run(this.spacefillRepr.create(structure, this.getSpacefillProps()), log, 100) + await Run(this.spacefillRepr.create(structure, this.getSpacefillProps()), log, 500) viewer.add(this.spacefillRepr) this.updateVisibility() @@ -138,6 +145,21 @@ export default class State { this.setModel((await getModelFromFile(file))[0]) } + async initVolume () { + const { viewer } = this + const v = this.volume.getValue() + if (!viewer || !v) return + + viewer.clear() + + this.surfaceRepr = VolumeRepresentation(Surface) + await Run(this.surfaceRepr.create(v.volume, { isoValue: VolumeIsoValue.relative(v.volume.dataStats, 1.5) }), log, 500) + viewer.add(this.surfaceRepr) + + viewer.requestDraw() + console.log(viewer.stats) + } + async loadPdbId () { this.viewer.clear() if (this.pdbId.length !== 4) return @@ -145,10 +167,23 @@ export default class State { this.setModel((await getModelFromPdbId(this.pdbId))[0]) } + setVolume(volume: Volume) { + this.volume.next(volume) + this.initVolume() + this.loading.next(false) + } + + async loadEmdId () { + this.viewer.clear() + if (this.emdId.length !== 4) return + this.loading.next(true) + this.setVolume(await getVolumeFromEmdId(this.emdId)) + } + async update () { if (!this.spacefillRepr) return - await Run(this.spacefillRepr.update(this.getSpacefillProps()), log, 100) - await Run(this.pointRepr.update(this.getPointProps()), log, 100) + await Run(this.spacefillRepr.update(this.getSpacefillProps()), log, 500) + await Run(this.pointRepr.update(this.getPointProps()), log, 500) this.viewer.add(this.spacefillRepr) this.viewer.add(this.pointRepr) this.viewer.update() diff --git a/src/apps/render-test/ui.tsx b/src/apps/render-test/ui.tsx index 2ae6b9fb9119ede88d9b85153527170fae851125..b302fe512d682a377e6f054c92e85fd9dcd48433 100644 --- a/src/apps/render-test/ui.tsx +++ b/src/apps/render-test/ui.tsx @@ -16,7 +16,7 @@ import State from './state' import Viewport from './components/viewport' import FileInput from './components/file-input' import ColorTheme from './components/color-theme' -import Detail from './components/detail' +import SphereDetail from './components/sphere-detail' import Visibility from './components/visibility' import Assemblies from './components/assemblies' @@ -86,7 +86,7 @@ class UI extends React.Component<{ state: State } & WithStyles, { }> { <div> <Assemblies state={state} classes={classes}></Assemblies> <ColorTheme state={state} classes={classes}></ColorTheme> - <Detail state={state} classes={classes}></Detail> + <SphereDetail state={state} classes={classes}></SphereDetail> <Visibility state={state} classes={classes}></Visibility> </div> </form> diff --git a/src/apps/render-test/utils/index.ts b/src/apps/render-test/utils/index.ts index 3225ea529bb68181355139fea35389f62fce238d..54486ffb3323d4808327e4ed013c332e38ed04de 100644 --- a/src/apps/render-test/utils/index.ts +++ b/src/apps/render-test/utils/index.ts @@ -7,23 +7,29 @@ import CIF from 'mol-io/reader/cif' import { Run, Progress } from 'mol-task' import { Model } from 'mol-model/structure' +import { VolumeData, parseDensityServerData } from 'mol-model/volume' +import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server'; export function log(progress: Progress) { const p = progress.root.progress console.log(`${p.message} ${(p.current/p.max*100).toFixed(2)}%`) } +export async function downloadCif(url: string, isBinary: boolean) { + const data = await fetch(url); + return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text()); +} + export async function parseCif(data: string|Uint8Array) { const comp = CIF.parse(data) - const parsed = await Run(comp, log, 100); + const parsed = await Run(comp, log, 500); if (parsed.isError) throw parsed; - return parsed + return parsed.result } export async function getModelFromPdbId(pdbid: string) { - const data = await fetch(`https://files.rcsb.org/download/${pdbid}.cif`) - const parsed = await parseCif(await data.text()) - return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) }) + const cif = await downloadCif(`https://files.rcsb.org/download/${pdbid}.cif`, false) + return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) }) } const readFileAsText = (file: File) => { @@ -39,6 +45,14 @@ const readFileAsText = (file: File) => { } export async function getModelFromFile(file: File) { - const parsed = await parseCif(await readFileAsText(file)) - return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) }) + const cif = await parseCif(await readFileAsText(file)) + return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) }) +} + +export type Volume = { source: DensityServer_Data_Database, volume: VolumeData } + +export async function getVolumeFromEmdId(emdid: string): Promise<Volume> { + const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, true) + const data = CIF.schema.densityServer(cif.blocks[1]) + return { source: data, volume: await Run(parseDensityServerData(data)) } } \ No newline at end of file diff --git a/src/apps/structure-info/volume.ts b/src/apps/structure-info/volume.ts index bfe15e630c66fb2b3e41cea24969a50c99fb4852..325e268dd7779e311408fe7c72280b6a7acac48c 100644 --- a/src/apps/structure-info/volume.ts +++ b/src/apps/structure-info/volume.ts @@ -14,7 +14,7 @@ import CIF from 'mol-io/reader/cif' import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server'; import { Run } from 'mol-task'; import { Table } from 'mol-data/db'; -import { computeVolumeMesh } from 'mol-geo/representation/volume/Mesh'; +import { computeVolumeSurface } from 'mol-geo/representation/volume/surface'; import { StringBuilder } from 'mol-util'; require('util.promisify').shim(); @@ -38,7 +38,7 @@ function print(data: Volume) { } async function doMesh(data: Volume, filename: string) { - const mesh = await Run(computeVolumeMesh(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5))); + const mesh = await Run(computeVolumeSurface(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5))); console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount }); // Export the mesh in OBJ format. diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..509f85727063511ed217f9d1f527c65d2cf34dd4 --- /dev/null +++ b/src/mol-geo/representation/index.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Task } from 'mol-task' +import { RenderObject } from 'mol-gl/scene'; + +export interface RepresentationProps {} + +export interface Representation<D, P extends RepresentationProps = {}> { + renderObjects: ReadonlyArray<RenderObject> + create: (data: D, props?: P) => Task<void> + update: (props: P) => Task<void> +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 5d722cec7c878ac4d381e580b812138ba9778c45..274da6a077bbfdeedcf8449020d710e27647cfe0 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -9,22 +9,20 @@ import { EquivalenceClasses } from 'mol-data/util'; import { OrderedSet } from 'mol-data/int' import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/scene'; +import { Representation, RepresentationProps } from '..'; // import { Mat4, EPSILON } from 'mol-math/linear-algebra'; -export interface RepresentationProps { -} - -export interface UnitsRepresentation<Props = {}> { +export interface UnitsRepresentation<P> { renderObjects: ReadonlyArray<RenderObject> - create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: Props) => Task<void> - update: (props: RepresentationProps) => Task<boolean> + create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: P) => Task<void> + update: (props: P) => Task<boolean> } -export interface StructureRepresentation<Props = {}> { +export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { renderObjects: ReadonlyArray<RenderObject> - create: (structure: Structure, props?: Props) => Task<void> - update: (props: Props) => Task<void> + create: (structure: Structure, props?: P) => Task<void> + update: (props: P) => Task<void> } interface GroupRepresentation<T> { @@ -33,13 +31,13 @@ interface GroupRepresentation<T> { elementGroup: ElementGroup } -export function StructureRepresentation<Props>(reprCtor: () => UnitsRepresentation<Props>): StructureRepresentation<Props> { +export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> { const renderObjects: RenderObject[] = [] - const groupReprs: GroupRepresentation<Props>[] = [] + const groupReprs: GroupRepresentation<P>[] = [] return { renderObjects, - create(structure: Structure, props: Props = {} as Props) { + create(structure: Structure, props: P = {} as P) { return Task.create('StructureRepresentation.create', async ctx => { const { elements, units } = structure; const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>( @@ -73,20 +71,20 @@ export function StructureRepresentation<Props>(reprCtor: () => UnitsRepresentati const elementGroup = ElementSet.groupFromUnitIndex(elements, group[0]) const repr = reprCtor() groupReprs.push({ repr, units: groupUnits, elementGroup }) - await ctx.update({ message: 'Building units...', current: i, max: il }); + await ctx.update({ message: 'Building structure unit representations...', current: i, max: il }); await ctx.runChild(repr.create(groupUnits, elementGroup, props)); renderObjects.push(...repr.renderObjects) } }); }, - update(props: Props) { + update(props: P) { return Task.create('StructureRepresentation.update', async ctx => { // TODO check model.id, conformation.id, unit.id, elementGroup(.hashCode/.areEqual) renderObjects.length = 0 // clear for (let i = 0, il = groupReprs.length; i < il; ++i) { const groupRepr = groupReprs[i] const { repr, units, elementGroup } = groupRepr - await ctx.update({ message: 'Updating units...', current: i, max: il }); + await ctx.update({ message: 'Updating structure unit representations...', current: i, max: il }); if (!await ctx.runChild(repr.update(props))) { await ctx.runChild(repr.create(units, elementGroup, props)) } diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index 73884615d0dbaf21f78d746862c4d42b89cbc86e..8a9d28fa61298d8e752ad4997772254052d4e42a 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -11,7 +11,7 @@ import { Unit, ElementGroup, Element } from 'mol-model/structure'; import { Task } from 'mol-task' import { fillSerial } from 'mol-gl/renderable/util'; -import { RepresentationProps, UnitsRepresentation } from './index'; +import { UnitsRepresentation } from './index'; import VertexMap from '../../shape/vertex-map'; import { ColorTheme, SizeTheme } from '../../theme'; import { createTransforms, createColors, createSizes } from './utils'; @@ -100,7 +100,7 @@ export default function Point(): UnitsRepresentation<PointProps> { renderObjects.push(points) }) }, - update(props: RepresentationProps) { + update(props: PointProps) { return Task.create('Point.update', async ctx => { if (!points || !_units || !_elementGroup) return false diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 47d238d2de34c249e29b7bdbe3f7afa217d2fc88..1362927bf4597208785b9337dcea83df5d1cb419 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -11,7 +11,7 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/s import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { OrderedSet } from 'mol-data/int' import { Unit, ElementGroup, Element, Queries } from 'mol-model/structure'; -import { RepresentationProps, UnitsRepresentation } from './index'; +import { UnitsRepresentation } from './index'; import { Task } from 'mol-task' import { MeshBuilder } from '../../shape/mesh-builder'; import { createTransforms, createColors } from './utils'; @@ -110,7 +110,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { renderObjects.push(spheres) }) }, - update(props: RepresentationProps) { + update(props: SpacefillProps) { return Task.create('Spacefill.update', async ctx => { if (!spheres) return false diff --git a/src/mol-geo/representation/volume/Mesh.ts b/src/mol-geo/representation/volume/Mesh.ts deleted file mode 100644 index 317b51b3ecb968e1daaa8a6c807bc1f9c6bb529a..0000000000000000000000000000000000000000 --- a/src/mol-geo/representation/volume/Mesh.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { VolumeData, VolumeIsoValue } from 'mol-model/volume' -import { Task } from 'mol-task' -import { Mesh } from '../../shape/mesh'; -import { computeMarchingCubes } from '../../util/marching-cubes/algorithm'; - -function computeVolumeMesh(volume: VolumeData, isoValue: VolumeIsoValue) { - return Task.create<Mesh>('Volume Surface', async ctx => { - ctx.update({ message: 'Marching cubes...' }); - - const mesh = await ctx.runChild(computeMarchingCubes({ - isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue, - scalarField: volume.data - })); - - const transform = VolumeData.getGridToCartesianTransform(volume); - ctx.update({ message: 'Transforming mesh...' }); - Mesh.transformImmediate(mesh, transform); - - return mesh; - }); -} - -export { computeVolumeMesh } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..84aee724fff635612391ba5d63db2f2df2e8f55e --- /dev/null +++ b/src/mol-geo/representation/volume/index.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Task } from 'mol-task' +import { RenderObject } from 'mol-gl/scene'; +import { RepresentationProps, Representation } from '..'; +import { VolumeData } from 'mol-model/volume'; + +export interface VolumeElementRepresentation<P> { + renderObjects: ReadonlyArray<RenderObject> + create: (volumeData: VolumeData, props: P) => Task<void> + update: (props: P) => Task<boolean> +} + +export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { + renderObjects: ReadonlyArray<RenderObject> + create: (volumeData: VolumeData, props?: P) => Task<void> + update: (props: P) => Task<void> +} + +export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentation<P>): VolumeRepresentation<P> { + const renderObjects: RenderObject[] = [] + + return { + renderObjects, + create(volumeData: VolumeData, props: P = {} as P) { + return Task.create('VolumeRepresentation.create', async ctx => { + const repr = reprCtor() + await ctx.update({ message: 'Building volume representation...', current: 0, max: 1 }); + await ctx.runChild(repr.create(volumeData, props)); + renderObjects.push(...repr.renderObjects) + }); + }, + update(props: P) { + return Task.create('VolumeRepresentation.update', async ctx => {}) + } + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts new file mode 100644 index 0000000000000000000000000000000000000000..2298ed467b8e21682453973db92b5b8fda68eaff --- /dev/null +++ b/src/mol-geo/representation/volume/surface.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { VolumeData, VolumeIsoValue } from 'mol-model/volume' +import { Task } from 'mol-task' +import { computeMarchingCubes } from '../../util/marching-cubes/algorithm'; +import { Mesh } from '../../shape/mesh'; +import { VolumeElementRepresentation } from '.'; +import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/scene'; +import { fillSerial } from 'mol-gl/renderable/util'; +import { ValueCell } from 'mol-util'; +import { Mat4 } from 'mol-math/linear-algebra'; +import { createUniformColor } from '../../util/color-data'; + +export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) { + return Task.create<Mesh>('Volume Surface', async ctx => { + ctx.update({ message: 'Marching cubes...' }); + + const mesh = await ctx.runChild(computeMarchingCubes({ + isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue, + scalarField: volume.data + })); + + const transform = VolumeData.getGridToCartesianTransform(volume); + ctx.update({ message: 'Transforming mesh...' }); + Mesh.transformImmediate(mesh, transform); + + return mesh; + }); +} + +export const DefaultSurfaceProps = { + isoValue: VolumeIsoValue.relative({ min: 0, max: 0, mean: 0, sigma: 0 }, 0) +} +export type SurfaceProps = Partial<typeof DefaultSurfaceProps> + +export default function Surface(): VolumeElementRepresentation<SurfaceProps> { + const renderObjects: RenderObject[] = [] + let surface: MeshRenderObject + let curProps = DefaultSurfaceProps + + return { + renderObjects, + create(volume: VolumeData, props: SurfaceProps = {}) { + return Task.create('Point.create', async ctx => { + renderObjects.length = 0 // clear + curProps = { ...DefaultSurfaceProps, ...props } + + const mesh = await ctx.runChild(computeVolumeSurface(volume, curProps.isoValue)) + + surface = createMeshRenderObject({ + objectId: 0, + + position: mesh.vertexBuffer, + normal: mesh.normalBuffer, + id: ValueCell.create(fillSerial(new Float32Array(mesh.vertexCount / 3))), + color: createUniformColor({ value: 0x999999 }), + transform: ValueCell.create(new Float32Array(Mat4.identity())), + index: mesh.indexBuffer, + + instanceCount: 1, + indexCount: mesh.triangleCount, + elementCount: mesh.triangleCount, + positionCount: mesh.vertexCount / 3, + + flatShaded: true + }) + renderObjects.push(surface) + }) + }, + update(props: SurfaceProps) { + return Task.create('Surface.update', async ctx => { + // TODO + return false + }) + } + } +} diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index 3b6a0a63af928a641656423b6372dd521c470367..38d202788c73b83452392fe1bbef57b71f39bacc 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -20,7 +20,7 @@ namespace Mesh { objectId: number position: ValueCell<Float32Array> - normal?: ValueCell<Float32Array> + normal: ValueCell<Float32Array | undefined> id: ValueCell<Float32Array> color: ColorData @@ -31,12 +31,19 @@ namespace Mesh { instanceCount: number elementCount: number positionCount: number + + flatShaded?: boolean + doubleSided?: boolean } export function create(ctx: Context, props: Props): Renderable<Props> { + const defines = getBaseDefines(props) + if (props.flatShaded) defines.FLAT_SHADED = '' + if (props.doubleSided) defines.DOUBLE_SIDED = '' + const defs: RenderItemProps = { ...getBaseDefs(props), - shaderCode: addShaderDefines(getBaseDefines(props), MeshShaderCode), + shaderCode: addShaderDefines(defines, MeshShaderCode), drawMode: 'triangles', elementsKind: 'uint32' } diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index e964f06f5194131034a9401b9e23cfd3e370d653..59caeafaa2676dc4dd6403b4c0686e23255ad905 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -67,7 +67,7 @@ interface BaseProps { positionCount: number, position: ValueCell<Float32Array> - normal?: ValueCell<Float32Array> + normal?: ValueCell<Float32Array | undefined> id: ValueCell<Float32Array> transform: ValueCell<Float32Array> @@ -133,7 +133,7 @@ export function getBaseAttributeDefs(props: BaseProps) { elementId: { kind: 'float32', itemSize: 1, divisor: 0 }, transform: { kind: 'float32', itemSize: 16, divisor: 1 }, } - if (props.normal) { + if (props.normal && props.normal.ref.value) { attributeDefs.normal = { kind: 'float32', itemSize: 3, divisor: 0 } } const color = props.color @@ -156,7 +156,7 @@ export function getBaseAttributeValues(props: BaseProps) { elementId: id.ref.value, transform: transform.ref.value } - if (normal) { + if (normal && normal.ref.value) { attributeValues.normal = normal.ref.value } const color = props.color diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 0b51b0dc56720e08e4479b333e4ab83ac6eebb5a..0777c372677928ad9cae4db2a8162b7d001d6b92 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -23,7 +23,8 @@ export const MeshShaderCode: ShaderCode = { type ShaderDefine = ( 'UNIFORM_COLOR' | 'ATTRIBUTE_COLOR' | 'INSTANCE_COLOR' | 'ELEMENT_COLOR' | 'ELEMENT_INSTANCE_COLOR' | 'UNIFORM_SIZE' | 'ATTRIBUTE_SIZE' | - 'POINT_SIZE_ATTENUATION' + 'POINT_SIZE_ATTENUATION' | + 'FLAT_SHADED' | 'DOUBLE_SIDED' ) export type ShaderDefines = { [k in ShaderDefine]?: number|string diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index d8961c4ada640c6de497c38e2ee2741e541e0551..89191d411a5b4691d4eea3e058d25aa9c5534150 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/src/mol-gl/shader/mesh.frag @@ -4,6 +4,10 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +#ifdef FLAT_SHADED + #extension GL_OES_standard_derivatives : enable +#endif + precision highp float; // uniform vec3 light_position; @@ -11,7 +15,10 @@ uniform vec3 light_color; uniform vec3 light_ambient; uniform mat4 view; -varying vec3 vNormal, vViewPosition; +#ifndef FLAT_SHADED + varying vec3 vNormal; +#endif +varying vec3 vViewPosition; #pragma glslify: import('./chunks/color-frag-params.glsl') @@ -35,7 +42,18 @@ void main() { vec3 L = normalize(lightVector); // light direction vec3 V = normalize(vViewPosition); // eye direction - vec3 N = normalize(-vNormal); // surface normal + + // surface normal + #ifdef FLAT_SHADED + vec3 fdx = dFdx(vViewPosition); + vec3 fdy = dFdy(vViewPosition); + vec3 N = -normalize(cross(fdx, fdy)); + #else + vec3 N = -normalize(vNormal); + #ifdef DOUBLE_SIDED + N = N * (float(gl_FrontFacing) * 2.0 - 1.0); + #endif + #endif // compute our diffuse & specular terms float specular = calculateSpecular(L, V, N, shininess) * specularScale; diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert index 4684fc84414be2c12b007111d9b6f6c303a090f9..1206fda5d3df3444d79569efa04c7d705f19199c 100644 --- a/src/mol-gl/shader/mesh.vert +++ b/src/mol-gl/shader/mesh.vert @@ -19,9 +19,11 @@ attribute mat4 transform; attribute float instanceId; attribute float elementId; -attribute vec3 normal; +#ifndef FLAT_SHADED + attribute vec3 normal; + varying vec3 vNormal; +#endif -varying vec3 vNormal; varying vec3 vViewPosition; #pragma glslify: inverse = require(./utils/inverse.glsl) @@ -35,6 +37,8 @@ void main(){ vViewPosition = mvPosition.xyz; gl_Position = projection * mvPosition; - mat3 normalMatrix = transpose(inverse(mat3(modelView))); - vNormal = normalize(normalMatrix * normal); + #ifndef FLAT_SHADED + mat3 normalMatrix = transpose(inverse(mat3(modelView))); + vNormal = normalize(normalMatrix * normal); + #endif } \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index b7162e29377e2977373b8649f817b67f0c4f8f87..d5f987e280a491207ff468304619721d12cd9034 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -33,6 +33,7 @@ function unbindResources (gl: WebGLRenderingContext) { type Extensions = { angleInstancedArrays: ANGLE_instanced_arrays + standardDerivatives: OES_standard_derivatives oesElementIndexUint: OES_element_index_uint | null oesVertexArrayObject: OES_vertex_array_object | null } @@ -53,6 +54,10 @@ export function createContext(gl: WebGLRenderingContext): Context { if (angleInstancedArrays === null) { throw new Error('Could not get "ANGLE_instanced_arrays" extension') } + const standardDerivatives = gl.getExtension('OES_standard_derivatives') + if (standardDerivatives === null) { + throw new Error('Could not get "OES_standard_derivatives" extension') + } const oesElementIndexUint = gl.getExtension('OES_element_index_uint') if (oesElementIndexUint === null) { console.warn('Could not get "OES_element_index_uint" extension') @@ -67,7 +72,7 @@ export function createContext(gl: WebGLRenderingContext): Context { return { gl, - extensions: { angleInstancedArrays, oesElementIndexUint, oesVertexArrayObject }, + extensions: { angleInstancedArrays, standardDerivatives, oesElementIndexUint, oesVertexArrayObject }, shaderCache, programCache, bufferCount: 0, diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 7fef68f8545115e60d9cc8c39910d5050426c9f7..22169186d2e350fae3ae7d181d53b95e189f329e 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -9,20 +9,20 @@ import InputObserver from 'mol-util/input/input-observer' import * as SetUtils from 'mol-util/set' import Renderer, { RendererStats } from 'mol-gl/renderer' import { RenderObject } from 'mol-gl/scene' -import { StructureRepresentation } from 'mol-geo/representation/structure'; import TrackballControls from './controls/trackball' import { Viewport } from './camera/util' import { PerspectiveCamera } from './camera/perspective' import { resizeCanvas } from './util'; import { createContext } from 'mol-gl/webgl/context'; +import { Representation } from 'mol-geo/representation'; interface Viewer { - hide: (repr: StructureRepresentation) => void - show: (repr: StructureRepresentation) => void + hide: (repr: Representation<any>) => void + show: (repr: Representation<any>) => void - add: (repr: StructureRepresentation) => void - remove: (repr: StructureRepresentation) => void + add: (repr: Representation<any>) => void + remove: (repr: Representation<any>) => void update: () => void clear: () => void @@ -49,7 +49,7 @@ function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLCon namespace Viewer { export function create(canvas: HTMLCanvasElement, container: Element): Viewer { - const reprMap = new Map<StructureRepresentation, Set<RenderObject>>() + const reprMap = new Map<Representation<any>, Set<RenderObject>>() const input = InputObserver.create(canvas) input.resize.subscribe(handleResize) @@ -99,16 +99,16 @@ namespace Viewer { handleResize() return { - hide: (repr: StructureRepresentation) => { + hide: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) if (renderObjectSet) renderObjectSet.forEach(o => o.visible = false) }, - show: (repr: StructureRepresentation) => { + show: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) if (renderObjectSet) renderObjectSet.forEach(o => o.visible = true) }, - add: (repr: StructureRepresentation) => { + add: (repr: Representation<any>) => { const oldRO = reprMap.get(repr) const newRO = new Set<RenderObject>() repr.renderObjects.forEach(o => newRO.add(o)) @@ -120,7 +120,7 @@ namespace Viewer { } reprMap.set(repr, newRO) }, - remove: (repr: StructureRepresentation) => { + remove: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) if (renderObjectSet) renderObjectSet.forEach(o => renderer.remove(o)) },