diff --git a/src/apps/render-test/components/assemblies.tsx b/src/apps/render-test/components/assemblies.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ab0bae997994a229b33d5ac3419e72e484f218f9 --- /dev/null +++ b/src/apps/render-test/components/assemblies.tsx @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import { WithStyles } from 'material-ui/styles'; +import { MenuItem } from 'material-ui/Menu'; +import { InputLabel } from 'material-ui/Input'; +import { FormControl } from 'material-ui/Form'; +import Select from 'material-ui/Select'; + +import State from '../state' +import Observer from './observer'; +import { Assembly } from 'mol-model/structure/model/properties/symmetry'; + +interface AssemblyState { + loading: boolean + assemblies: ReadonlyArray<Assembly> + value: string +} + +export default class Assemblies extends Observer<{ state: State } & WithStyles, AssemblyState> { + state: AssemblyState = { loading: false, assemblies: [], value: '' } + + componentDidMount() { + this.subscribe(this.props.state.loading, value => { + this.setState({ loading: value }); + }); + this.subscribe(this.props.state.model, value => { + this.setState({ assemblies: value ? value.symmetry.assemblies : [] }); + }); + this.subscribe(this.props.state.assembly, value => { + this.setState({ value }); + }); + } + + handleValueChange = (event: React.ChangeEvent<any>) => { + this.props.state.assembly.next(event.target.value) + } + + render() { + const { classes } = this.props; + + const items = this.state.assemblies.map((value, idx) => { + return <MenuItem key={idx} value={value.id}>{value.details}</MenuItem> + }) + + return <FormControl className={classes.formControl}> + <InputLabel htmlFor='assembly-value'>Assembly</InputLabel> + <Select + className={classes.selectField} + value={this.state.value} + onChange={this.handleValueChange} + inputProps={{ + name: 'value', + id: 'assembly-value', + }} + > + {items} + </Select> + </FormControl> + } +} \ No newline at end of file diff --git a/src/apps/render-test/components/color-theme.tsx b/src/apps/render-test/components/color-theme.tsx index 9f80a040225e12616fae4ca26f8ff61253de1678..701247c5db1bc0e2c0ed79caa486f711b2dc99ac 100644 --- a/src/apps/render-test/components/color-theme.tsx +++ b/src/apps/render-test/components/color-theme.tsx @@ -22,7 +22,7 @@ interface ColorThemeState { } export default class ColorTheme extends Observer<{ state: State } & WithStyles, ColorThemeState> { - state = { loading: false, name: 'element-symbol' as _ColorTheme, value: 0xFF0000 } + state: ColorThemeState = { loading: false, name: 'element-symbol' as _ColorTheme, value: 0xFF0000 } componentDidMount() { this.subscribe(this.props.state.loading, value => { diff --git a/src/apps/render-test/components/detail.tsx b/src/apps/render-test/components/sphere-detail.tsx similarity index 72% rename from src/apps/render-test/components/detail.tsx rename to src/apps/render-test/components/sphere-detail.tsx index 65c5eb8e55732b4a54ff1a5e86d2399687935ccc..1b19cca359238d3b5d4a0e99dfb80c7d4ef03c99 100644 --- a/src/apps/render-test/components/detail.tsx +++ b/src/apps/render-test/components/sphere-detail.tsx @@ -14,43 +14,43 @@ 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 = { 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() { const { classes } = this.props; - const items = [0, 1, 2].map((value, idx) => { + const items = [0, 1, 2, 3].map((value, idx) => { return <MenuItem key={idx} value={value}>{value.toString()}</MenuItem> }) 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/components/visibility.tsx b/src/apps/render-test/components/visibility.tsx index 86f9e44227e7749d352a9ca9580ae99165c75e50..a93a9b123ba729f5488cfb159339f731cfc921c0 100644 --- a/src/apps/render-test/components/visibility.tsx +++ b/src/apps/render-test/components/visibility.tsx @@ -19,7 +19,7 @@ interface VisibilityState { } export default class Visibility extends Observer<{ state: State } & WithStyles, VisibilityState> { - state = { loading: false, spacefill: true, point: true } + state: VisibilityState = { loading: false, spacefill: true, point: true } componentDidMount() { this.subscribe(this.props.state.loading, value => { diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts index c2616d4970b52d6ac0dbe104494fa3072915b4b1..1e284a0a12b47738f2d32ca9dd550e07f9fbdbcd 100644 --- a/src/apps/render-test/state.ts +++ b/src/apps/render-test/state.ts @@ -17,12 +17,15 @@ import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spac import Point, { PointProps } from 'mol-geo/representation/structure/point' import { Run } from 'mol-task' -import { Symmetry, Structure } from 'mol-model/structure' +import { Symmetry, Structure, Model } from 'mol-model/structure' // import mcubes from './utils/mcubes' -import { getStructuresFromPdbId, getStructuresFromFile, 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,24 +40,35 @@ export type ColorTheme = keyof typeof ColorTheme export default class State { viewer: Viewer - pdbId = '4cup' + pdbId = '1crn' + // pdbId = '5ire' + emdId = '8116' + // pdbId = '6G1K' + // emdId = '4339' + // pdbId = '4cup' + // emdId = '' + 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) spacefillVisibility = new BehaviorSubject<boolean>(true) 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()) this.spacefillVisibility.subscribe(() => this.updateVisibility()) @@ -63,7 +77,8 @@ export default class State { getSpacefillProps (): SpacefillProps { const colorThemeName = this.colorTheme.getValue() return { - detail: this.detail.getValue(), + doubleSided: true, + detail: this.sphereDetail.getValue(), colorTheme: colorThemeName === 'uniform' ? { name: colorThemeName, value: this.colorValue.getValue() } : { name: colorThemeName } @@ -84,51 +99,105 @@ export default class State { this.viewer = Viewer.create(canvas, container) this.initialized.next(true) this.loadPdbId() + this.loadEmdId() this.viewer.animate() } - async initStructure (structure: Structure) { - const { viewer, loading } = this - viewer.clear() + async getStructure () { + const model = this.model.getValue() + if (!model) return + const assembly = this.assembly.getValue() + let structure: Structure + const assemblies = model.symmetry.assemblies + if (assemblies.length) { + structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), log, 500) + } else { + structure = Structure.ofModel(model) + } + return structure + } + + async initStructure () { + const { viewer } = this + if (!viewer || !this.model.getValue()) return - const struct = await Run(Symmetry.buildAssembly(structure, '1'), log, 100) + if (this.pointRepr) this.viewer.remove(this.pointRepr) + if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr) + + const structure = await this.getStructure() + if (!structure) return this.pointRepr = StructureRepresentation(Point) - await Run(this.pointRepr.create(struct, 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(struct, this.getSpacefillProps()), log, 100) + await Run(this.spacefillRepr.create(structure, this.getSpacefillProps()), log, 500) viewer.add(this.spacefillRepr) this.updateVisibility() viewer.requestDraw() console.log(viewer.stats) + } - loading.next(false) + setModel(model: Model) { + this.model.next(model) + this.initStructure() + this.loading.next(false) } async loadFile (file: File) { this.viewer.clear() this.loading.next(true) + this.setModel((await getModelFromFile(file))[0]) + } + + async initVolume () { + const { viewer } = this + const v = this.volume.getValue() + if (!viewer || !v) return + + if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr) - const structures = await getStructuresFromFile(file) - this.initStructure(structures[0]) + this.surfaceRepr = VolumeRepresentation(Surface) + await Run(this.surfaceRepr.create(v.volume, { + isoValue: VolumeIsoValue.relative(v.volume.dataStats, 3.0), + alpha: 0.5, + flatShaded: false, + flipSided: true, + doubleSided: true + }), log, 500) + viewer.add(this.surfaceRepr) + + viewer.requestDraw() + console.log(viewer.stats) } async loadPdbId () { - this.viewer.clear() + if (this.pointRepr) this.viewer.remove(this.pointRepr) + if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr) if (this.pdbId.length !== 4) return this.loading.next(true) + this.setModel((await getModelFromPdbId(this.pdbId))[0]) + } + + setVolume(volume: Volume) { + this.volume.next(volume) + this.initVolume() + this.loading.next(false) + } - const structures = await getStructuresFromPdbId(this.pdbId) - this.initStructure(structures[0]) + async loadEmdId () { + if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr) + 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() @@ -154,87 +223,4 @@ export default class State { } this.viewer.requestDraw() } -} - - - -// async foo () { -// const p1 = Vec3.create(0, 4, 0) -// const p2 = Vec3.create(-3, 0, 0) - -// // const position = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0])) -// // const normal = ValueCell.create(new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0])) - -// const transformArray1 = ValueCell.create(new Float32Array(16)) -// const transformArray2 = ValueCell.create(new Float32Array(16 * 3)) -// const m4 = Mat4.identity() -// Mat4.toArray(m4, transformArray1.ref.value, 0) -// Mat4.toArray(m4, transformArray2.ref.value, 0) -// Mat4.setTranslation(m4, p1) -// Mat4.toArray(m4, transformArray2.ref.value, 16) -// Mat4.setTranslation(m4, p2) -// Mat4.toArray(m4, transformArray2.ref.value, 32) - -// const color = ValueCell.create(createColorTexture(3)) -// color.ref.value.set([ -// 0, 0, 255, -// 0, 255, 0, -// 255, 0, 0 -// ]) - -// // const points = createRenderObject('point', { -// // position, -// // transform: transformArray1 -// // }) -// // // renderer.add(points) - -// // const mesh = createRenderObject('mesh', { -// // position, -// // normal, -// // color, -// // transform: transformArray2 -// // }) -// // renderer.add(mesh) - -// // const cylinder = Cylinder({ height: 3, radiusBottom: 0.5, radiusTop: 0.5 }) -// // console.log(cylinder) -// // const cylinderMesh = createRenderObject('mesh', { -// // position: ValueCell.create(cylinder.vertices), -// // normal: ValueCell.create(cylinder.normals), -// // color, -// // transform: transformArray2 -// // }, cylinder.indices) -// // renderer.add(cylinderMesh) - -// // const sphere = Icosahedron() -// // console.log(sphere) - -// // const box = Box() -// // console.log(box) - -// // const points2 = createRenderObject('point', { -// // position: ValueCell.create(new Float32Array(box.vertices)), -// // transform: transformArray1 -// // }) -// // renderer.add(points2) - -// // let rr = 0.7; -// // function cubesF(x: number, y: number, z: number) { -// // return x * x + y * y + z * z - rr * rr; -// // } -// // let cubes = await mcubes(cubesF); - -// // const makeCubesMesh = () => createRenderObject('mesh', { -// // position: cubes.surface.vertexBuffer, -// // normal: cubes.surface.normalBuffer, -// // color, -// // transform: transformArray2, -// // elements: cubes.surface.indexBuffer, - -// // instanceCount: transformArray2.ref.value.length / 16, -// // elementCount: cubes.surface.triangleCount, -// // positionCount: cubes.surface.vertexCount -// // }, {}); -// // const mesh2 = makeCubesMesh(); -// // renderer.add(mesh2) -// } \ No newline at end of file +} \ No newline at end of file diff --git a/src/apps/render-test/ui.tsx b/src/apps/render-test/ui.tsx index 948a49d2f0d1133b5c48c0ab90bde9bc9a2ada71..b302fe512d682a377e6f054c92e85fd9dcd48433 100644 --- a/src/apps/render-test/ui.tsx +++ b/src/apps/render-test/ui.tsx @@ -16,8 +16,9 @@ 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' const styles: StyleRulesCallback = (theme: Theme) => ({ @@ -83,8 +84,9 @@ class UI extends React.Component<{ state: State } & WithStyles, { }> { <FileInput state={state} classes={classes}></FileInput> <form className={classes.root} autoComplete='off'> <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 738bd7851a542c6522ccd07cb869a73e11ee6891..54486ffb3323d4808327e4ed013c332e38ed04de 100644 --- a/src/apps/render-test/utils/index.ts +++ b/src/apps/render-test/utils/index.ts @@ -6,24 +6,30 @@ import CIF from 'mol-io/reader/cif' import { Run, Progress } from 'mol-task' -import { Structure } from 'mol-model/structure' +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 getStructuresFromPdbId(pdbid: string) { - const data = await fetch(`https://files.rcsb.org/download/${pdbid}.cif`) - const parsed = await parseCif(await data.text()) - return Structure.ofData({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) }) +export async function getModelFromPdbId(pdbid: string) { + 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) => { @@ -38,7 +44,15 @@ const readFileAsText = (file: File) => { }) } -export async function getStructuresFromFile(file: File) { - const parsed = await parseCif(await readFileAsText(file)) - return Structure.ofData({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) }) +export async function getModelFromFile(file: File) { + 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/primitive/icosahedron.ts b/src/mol-geo/primitive/icosahedron.ts index 7e73afb419daeef55278241fafe2cfef5f7dbeaf..07e544e07fcfdd7cddb36940173f2841c58da169 100644 --- a/src/mol-geo/primitive/icosahedron.ts +++ b/src/mol-geo/primitive/icosahedron.ts @@ -23,6 +23,10 @@ const indices = [ 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 ]; +export function icosahedronVertexCount(detail: number) { + return 10 * Math.pow(Math.pow(2, detail), 2) + 2 +} + export const DefaultIcosahedronProps = { radius: 1, detail: 0 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 b95347fb17e7035214d95963c38d896bf2002262..71f317e39c3a31e83cce6d0f63a3f25201406e29 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -7,11 +7,11 @@ import { ValueCell } from 'mol-util/value-cell' import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene' import { OrderedSet } from 'mol-data/int' -import { Unit, ElementGroup } from 'mol-model/structure'; +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'; @@ -19,20 +19,26 @@ import { deepEqual } from 'mol-util'; export const DefaultPointProps = { colorTheme: { name: 'instance-index' } as ColorTheme, - sizeTheme: { name: 'vdw' } as SizeTheme + sizeTheme: { name: 'vdw' } as SizeTheme, + alpha: 1, + visible: true } export type PointProps = Partial<typeof DefaultPointProps> export function createPointVertices(unit: Unit, elementGroup: ElementGroup) { const elementCount = OrderedSet.size(elementGroup.elements) const vertices = new Float32Array(elementCount * 3) - const { x, y, z } = unit.model.atomSiteConformation + + const { x, y, z } = unit + const l = Element.Location() + l.unit = unit + for (let i = 0; i < elementCount; i++) { - const e = OrderedSet.getAt(elementGroup.elements, i) + l.element = ElementGroup.getAt(elementGroup, i) const i3 = i * 3 - vertices[i3] = x[e] - vertices[i3 + 1] = y[e] - vertices[i3 + 2] = z[e] + vertices[i3] = x(l.element) + vertices[i3 + 1] = y(l.element) + vertices[i3 + 2] = z(l.element) } return vertices } @@ -55,7 +61,7 @@ export default function Point(): UnitsRepresentation<PointProps> { _units = units _elementGroup = elementGroup - const { colorTheme, sizeTheme } = curProps + const { colorTheme, sizeTheme, alpha, visible } = curProps const elementCount = OrderedSet.size(elementGroup.elements) const unitCount = units.length @@ -80,6 +86,8 @@ export default function Point(): UnitsRepresentation<PointProps> { points = createPointRenderObject({ objectId: 0, + alpha, + visible, position: ValueCell.create(vertices), id: ValueCell.create(fillSerial(new Float32Array(elementCount))), @@ -96,7 +104,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 6f994adee431d2f060fd375b62031bf9d47a67b0..689f2bfc9bfe0bfac81346a6294d5ae2330e2620 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -10,46 +10,60 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/s // import { createColorTexture } from 'mol-gl/util'; import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { OrderedSet } from 'mol-data/int' -import { Unit, ElementGroup } from 'mol-model/structure'; -import { RepresentationProps, UnitsRepresentation } from './index'; +import { Unit, ElementGroup, Element, Queries } from 'mol-model/structure'; +import { UnitsRepresentation } from './index'; import { Task } from 'mol-task' import { MeshBuilder } from '../../shape/mesh-builder'; -import { VdwRadius } from 'mol-model/structure/model/properties/atomic'; import { createTransforms, createColors } from './utils'; import { ColorTheme } from '../../theme'; import VertexMap from '../../shape/vertex-map'; +import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained'; +import { icosahedronVertexCount } from '../../primitive/icosahedron'; export const DefaultSpacefillProps = { detail: 0, colorTheme: { name: 'instance-index' } as ColorTheme, + alpha: 1, + visible: true, + doubleSided: false } export type SpacefillProps = Partial<typeof DefaultSpacefillProps> function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: number) { - return Task.create('Spacefill', async ctx => { - const meshBuilder = MeshBuilder.create() + return Task.create('Sphere mesh', async ctx => { + const elementCount = OrderedSet.size(elementGroup.elements) + const vertexCount = elementCount * icosahedronVertexCount(detail) + const meshBuilder = MeshBuilder.create(vertexCount) + + let radius: Element.Property<number> + if (Unit.isAtomic(unit)) { + radius = Queries.props.atom.vdw_radius + } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) { + radius = Queries.props.coarse_grained.sphere_radius + } else { + console.warn('Unsupported unit type') + return meshBuilder.getMesh() + } const v = Vec3.zero() const m = Mat4.identity() - const { x, y, z } = unit.model.atomSiteConformation - const { type_symbol } = unit.model.hierarchy.atoms - const elementCount = OrderedSet.size(elementGroup.elements) + const { x, y, z } = unit + const l = Element.Location() + l.unit = unit + for (let i = 0; i < elementCount; i++) { - const e = OrderedSet.getAt(elementGroup.elements, i) - v[0] = x[e] - v[1] = y[e] - v[2] = z[e] + l.element = ElementGroup.getAt(elementGroup, i) + v[0] = x(l.element) + v[1] = y(l.element) + v[2] = z(l.element) Mat4.setTranslation(m, v) meshBuilder.setId(i) - meshBuilder.addIcosahedron(m, { - radius: VdwRadius(type_symbol.value(e)), - detail - }) + meshBuilder.addIcosahedron(m, { radius: radius(l), detail }) if (i % 10000 === 0 && ctx.shouldUpdate) { - await ctx.update({ message: 'Spacefill', current: i, max: elementCount }); + await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount }); } } @@ -67,7 +81,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { return Task.create('Spacefill.create', async ctx => { renderObjects.length = 0 // clear - const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props } + const { detail, colorTheme, alpha, visible, doubleSided } = { ...DefaultSpacefillProps, ...props } await ctx.update('Computing spacefill mesh'); const mesh = await ctx.runChild(createSpacefillMesh(units[0], elementGroup, detail)) @@ -83,6 +97,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { spheres = createMeshRenderObject({ objectId: 0, + alpha, + visible, + doubleSided, position: mesh.vertexBuffer, normal: mesh.normalBuffer as ValueCell<Float32Array>, @@ -99,7 +116,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/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts index a7a1d158781faa97ca8f3100319e74b4b2a05c0b..fba67c536e46e9447fd19b170299d25771b19996 100644 --- a/src/mol-geo/representation/structure/utils.ts +++ b/src/mol-geo/representation/structure/utils.ts @@ -9,10 +9,10 @@ import { Mat4 } from 'mol-math/linear-algebra' import { createUniformColor } from '../../util/color-data'; import { createUniformSize } from '../../util/size-data'; -import { vdwSizeData } from '../../theme/structure/size/vdw'; +import { elementSizeData } from '../../theme/structure/size/element'; import VertexMap from '../../shape/vertex-map'; import { ColorTheme, SizeTheme } from '../../theme'; -import { atomIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color'; +import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color'; export function createTransforms(units: ReadonlyArray<Unit>) { const unitCount = units.length @@ -26,7 +26,7 @@ export function createTransforms(units: ReadonlyArray<Unit>) { export function createColors(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, vertexMap: VertexMap, props: ColorTheme) { switch (props.name) { case 'atom-index': - return atomIndexColorData({ units, elementGroup, vertexMap }) + return elementIndexColorData({ units, elementGroup, vertexMap }) case 'chain-id': return chainIdColorData({ units, elementGroup, vertexMap }) case 'element-symbol': @@ -43,6 +43,6 @@ export function createSizes(units: ReadonlyArray<Unit>, elementGroup: ElementGro case 'uniform': return createUniformSize(props) case 'vdw': - return vdwSizeData({ units, elementGroup, vertexMap }) + return elementSizeData({ units, elementGroup, vertexMap }) } } \ No newline at end of file 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..a2de1a61d10cef902ee848ee9efb6ddab6572ead --- /dev/null +++ b/src/mol-geo/representation/volume/surface.ts @@ -0,0 +1,95 @@ +/** + * 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), + alpha: 0.5, + visible: true, + flatShaded: true, + flipSided: true, + doubleSided: true +} +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 { alpha, visible, flatShaded, flipSided, doubleSided } = curProps + + const mesh = await ctx.runChild(computeVolumeSurface(volume, curProps.isoValue)) + if (!flatShaded) { + Mesh.computeNormalsImmediate(mesh) + } + + surface = createMeshRenderObject({ + objectId: 0, + alpha, + visible, + + position: mesh.vertexBuffer, + normal: mesh.normalBuffer, + id: ValueCell.create(fillSerial(new Float32Array(mesh.vertexCount / 3))), + color: createUniformColor({ value: 0x7ec0ee }), + transform: ValueCell.create(new Float32Array(Mat4.identity())), + index: mesh.indexBuffer, + + instanceCount: 1, + indexCount: mesh.triangleCount, + elementCount: mesh.triangleCount, + positionCount: mesh.vertexCount / 3, + + flatShaded, + doubleSided, + flipSided + }) + renderObjects.push(surface) + }) + }, + update(props: SurfaceProps) { + return Task.create('Surface.update', async ctx => { + // TODO + return false + }) + } + } +} diff --git a/src/mol-geo/shape/mesh.ts b/src/mol-geo/shape/mesh.ts index fcd4ac207f5e8dfb879c3ecacdb4ef33d4525bd8..fcbb9c0f9f4bdf91acde9c734236d77e670a3222 100644 --- a/src/mol-geo/shape/mesh.ts +++ b/src/mol-geo/shape/mesh.ts @@ -8,7 +8,7 @@ import { Task } from 'mol-task' import { ValueCell } from 'mol-util' import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { Sphere3D } from 'mol-math/geometry' -import { transformPositionArray } from '../util'; +import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../util'; export interface Mesh { /** Number of vertices in the mesh */ @@ -86,7 +86,12 @@ export namespace Mesh { export function transformRangeImmediate(mesh: Mesh, t: Mat4, offset: number, count: number) { transformPositionArray(t, mesh.vertexBuffer.ref.value, offset, count) - // transformDirectionArray(n, mesh.normalBuffer.ref.value, offset, count) // TODO + // TODO normals transformation does not work for an unknown reason, ASR + // if (mesh.normalBuffer.ref.value) { + // const n = getNormalMatrix(Mat3.zero(), t) + // transformDirectionArray(n, mesh.normalBuffer.ref.value, offset, count) + // mesh.normalsComputed = true; + // } mesh.normalsComputed = false; // mesh.boundingSphere = void 0; } diff --git a/src/mol-geo/theme/structure/color/chain-id.ts b/src/mol-geo/theme/structure/color/chain-id.ts index a6bd36b1aad179b0f1ac8269c47fcaa357994a53..43dfc0db654e236eaf9af30e8ed49b16d033d5be 100644 --- a/src/mol-geo/theme/structure/color/chain-id.ts +++ b/src/mol-geo/theme/structure/color/chain-id.ts @@ -4,21 +4,32 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { ElementGroup, Model } from 'mol-model/structure'; +import { ElementGroup, Unit, Queries, Element } from 'mol-model/structure'; import { StructureColorDataProps } from '.'; import { createAttributeOrElementColor } from '../../../util/color-data'; import { ColorScale } from 'mol-util/color'; +import { Column } from 'mol-data/db'; -function createChainIdMap(model: Model) { - const { chains } = model.hierarchy - const { label_asym_id } = chains - +function createChainIdMap(unit: Unit) { const map = new Map<string, number>() let index = 0 - for (let i = 0, il = chains._rowCount; i < il; ++i) { - const chainId = label_asym_id.value(i) + let count: number + let asym_id: Column<string> + if (Unit.isAtomic(unit)) { + asym_id = unit.hierarchy.chains.label_asym_id + count = unit.hierarchy.chains._rowCount + } else if (Unit.isCoarse(unit)) { + asym_id = unit.siteBases.asym_id + count = unit.siteBases.count + } else { + console.warn('Unknown unit type') + return { map, count: index } + } + + for (let i = 0; i < count; ++i) { + const chainId = asym_id.value(i) if (map.get(chainId) === undefined) { map.set(chainId, index) index += 1 @@ -31,20 +42,25 @@ export function chainIdColorData(props: StructureColorDataProps) { const { units, elementGroup, vertexMap } = props const unit = units[0] - const { chains, chainSegments } = unit.model.hierarchy - const { label_asym_id } = chains - const { map, count } = createChainIdMap(unit.model) + const { map, count } = createChainIdMap(unit) const domain = [ 0, count - 1 ] const scale = ColorScale.create({ domain }) + let asym_id: Element.Property<string> + if (Unit.isAtomic(unit)) { + asym_id = Queries.props.chain.label_asym_id + } else if (Unit.isCoarse(unit)) { + asym_id = Queries.props.coarse_grained.asym_id + } + + const l = Element.Location() + l.unit = unit + return createAttributeOrElementColor(vertexMap, { colorFn: (elementIdx: number) => { - const aI = ElementGroup.getAt(elementGroup, elementIdx); - const cI = chainSegments.segmentMap[aI] - const chainId = label_asym_id.value(cI) - - return scale.color(map.get(chainId) || 0) + l.element = ElementGroup.getAt(elementGroup, elementIdx) + return scale.color(map.get(asym_id(l)) || 0) }, vertexMap }) diff --git a/src/mol-geo/theme/structure/color/atom-index.ts b/src/mol-geo/theme/structure/color/element-index.ts similarity index 91% rename from src/mol-geo/theme/structure/color/atom-index.ts rename to src/mol-geo/theme/structure/color/element-index.ts index 1bd61c99a132c2a85b027b3cbcb7c1fdb189be2b..b205043d46ffb5a1fee65485dc9f026644f7a2c6 100644 --- a/src/mol-geo/theme/structure/color/atom-index.ts +++ b/src/mol-geo/theme/structure/color/element-index.ts @@ -9,7 +9,7 @@ import { StructureColorDataProps } from '.'; import { OrderedSet } from 'mol-data/int'; import { createElementInstanceColor } from '../../../util/color-data'; -export function atomIndexColorData(props: StructureColorDataProps) { +export function elementIndexColorData(props: StructureColorDataProps) { const { units, elementGroup, vertexMap } = props const instanceCount = units.length const elementCount = OrderedSet.size(elementGroup.elements) diff --git a/src/mol-geo/theme/structure/color/index.ts b/src/mol-geo/theme/structure/color/index.ts index 435874109499156465610b627b86287d4f2d6b6c..82d5b22a6c6535744d2c1000f5d0948bdace8311 100644 --- a/src/mol-geo/theme/structure/color/index.ts +++ b/src/mol-geo/theme/structure/color/index.ts @@ -13,7 +13,7 @@ export interface StructureColorDataProps { vertexMap: VertexMap } -export { atomIndexColorData } from './atom-index' +export { elementIndexColorData } from './element-index' export { chainIdColorData } from './chain-id' export { elementSymbolColorData } from './element-symbol' export { instanceIndexColorData } from './instance-index' \ No newline at end of file diff --git a/src/mol-geo/theme/structure/size/element.ts b/src/mol-geo/theme/structure/size/element.ts new file mode 100644 index 0000000000000000000000000000000000000000..699bc071a499ab303d3e40d48c3befb465248afe --- /dev/null +++ b/src/mol-geo/theme/structure/size/element.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ElementGroup, Element, Unit, Queries } from 'mol-model/structure'; +import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained'; +import { StructureSizeDataProps } from '.'; +import { createAttributeSize } from '../../../util/size-data'; + +/** Create attribute data with the size of an element, i.e. vdw for atoms and radius for coarse spheres */ +export function elementSizeData(props: StructureSizeDataProps) { + const { units, elementGroup, vertexMap } = props + const unit = units[0] + let radius: Element.Property<number> + if (Unit.isAtomic(unit)) { + radius = Queries.props.atom.vdw_radius + } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) { + radius = Queries.props.coarse_grained.sphere_radius + } + const l = Element.Location() + l.unit = unit + return createAttributeSize({ + sizeFn: (elementIdx: number) => { + l.element = ElementGroup.getAt(elementGroup, elementIdx) + return radius(l) + }, + vertexMap + }) +} \ No newline at end of file diff --git a/src/mol-geo/theme/structure/size/index.ts b/src/mol-geo/theme/structure/size/index.ts index b178ad42b9f9877bf819b0f2da71aa95e661219c..22caed502febf7ab0dd05a25201fe1c960335f07 100644 --- a/src/mol-geo/theme/structure/size/index.ts +++ b/src/mol-geo/theme/structure/size/index.ts @@ -13,4 +13,4 @@ export interface StructureSizeDataProps { vertexMap: VertexMap } -export { vdwSizeData } from './vdw' \ No newline at end of file +export { elementSizeData } from './element' \ No newline at end of file diff --git a/src/mol-geo/theme/structure/size/vdw.ts b/src/mol-geo/theme/structure/size/vdw.ts deleted file mode 100644 index 3010380904ab0810d3d79ff5f0a5b0a0d1f7a921..0000000000000000000000000000000000000000 --- a/src/mol-geo/theme/structure/size/vdw.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { OrderedSet } from 'mol-data/int'; -import { VdwRadius } from 'mol-model/structure/model/properties/atomic'; -import { StructureSizeDataProps } from '.'; -import { createAttributeSize } from '../../../util/size-data'; - - -export function vdwSizeData(props: StructureSizeDataProps) { - const { units, elementGroup, vertexMap } = props - const { type_symbol } = units[0].model.hierarchy.atoms - return createAttributeSize({ - sizeFn: (elementIdx: number) => { - const e = OrderedSet.getAt(elementGroup.elements, elementIdx) - return VdwRadius(type_symbol.value(e)) - }, - vertexMap - }) -} \ No newline at end of file diff --git a/src/mol-geo/util.ts b/src/mol-geo/util.ts index 615b7c4f5ade78d292705741242ef3553e5009a0..ad2156c06c38cccc9ec3ae6b5b87745276785738 100644 --- a/src/mol-geo/util.ts +++ b/src/mol-geo/util.ts @@ -19,18 +19,29 @@ export function normalizeVec3Array<T extends Helpers.NumberArray> (a: T) { } } -const tmpV = Vec3.zero() +export function getNormalMatrix(out: Mat3, t: Mat4) { + Mat3.fromMat4(out, t) + Mat3.invert(out, out) + Mat3.transpose(out, out) + return out +} + +const tmpV3 = Vec3.zero() export function transformPositionArray (t: Mat4, array: Helpers.NumberArray, offset: number, count: number) { for (let i = 0, il = count * 3; i < il; i += 3) { - Vec3.fromArray(tmpV, array, offset + i) - Vec3.transformMat4(tmpV, tmpV, t) - Vec3.toArray(tmpV, array, offset + i) + Vec3.fromArray(tmpV3, array, offset + i) + Vec3.transformMat4(tmpV3, tmpV3, t) + Vec3.toArray(tmpV3, array, offset + i) } } -export function transformDirectionArray (t: Mat3, array: Helpers.NumberArray, offset: number, count: number) { - // TODO +export function transformDirectionArray (n: Mat3, array: Helpers.NumberArray, offset: number, count: number) { + for (let i = 0, il = count * 3; i < il; i += 3) { + Vec3.fromArray(tmpV3, array, offset + i) + Vec3.transformMat3(tmpV3, tmpV3, n) + Vec3.toArray(tmpV3, array, offset + i) + } } export function setArrayZero(array: Helpers.NumberArray) { diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 7465af899e56b57f7f1f8dfe29a1a58feb7886f0..fc669697aacf4f56b162b01040528908c06e5507 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -50,6 +50,8 @@ function createPoints() { return createPointRenderObject({ objectId: 0, + alpha: 1.0, + visible: true, position, id, diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index fc4228d4b288483e1ffda04a2ce12310c9f229a1..d889f89c64a18487b75f82524ed125813dfec47e 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -8,6 +8,16 @@ import PointRenderable from './renderable/point' import MeshRenderable from './renderable/mesh' import { Program } from './webgl/program'; +export type BaseProps = { + objectId: number + alpha: number + visible: boolean + + flatShaded?: boolean + doubleSided?: boolean + flipSided?: boolean +} + export interface Renderable<T> { draw: () => void name: string diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index 3b6a0a63af928a641656423b6372dd521c470367..71ddafacecf8bb7affb5f2eba98debb8d004a066 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -7,7 +7,7 @@ import { ValueCell } from 'mol-util/value-cell' import { ColorData } from 'mol-geo/util/color-data'; -import { Renderable } from '../renderable' +import { Renderable, BaseProps } from '../renderable' import { getBaseDefs, getBaseValues, getBaseDefines } from './util' import { MeshShaderCode, addShaderDefines } from '../shader-code' import { Context } from '../webgl/context'; @@ -17,10 +17,8 @@ type Mesh = 'mesh' namespace Mesh { export type Props = { - objectId: number - position: ValueCell<Float32Array> - normal?: ValueCell<Float32Array> + normal: ValueCell<Float32Array | undefined> id: ValueCell<Float32Array> color: ColorData @@ -31,12 +29,17 @@ namespace Mesh { instanceCount: number elementCount: number positionCount: number - } + } & BaseProps 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 = '' + if (props.flipSided) defines.FLIP_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/point.ts b/src/mol-gl/renderable/point.ts index 7ff653779138a7afd7275f48bc2e0df0143c255a..83b206dde577e0aad08a15f3b735334bfd9e7b4a 100644 --- a/src/mol-gl/renderable/point.ts +++ b/src/mol-gl/renderable/point.ts @@ -6,7 +6,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { Renderable } from '../renderable' +import { Renderable, BaseProps } from '../renderable' import { getBaseValues, getBaseDefs, getBaseDefines } from './util' import { PointShaderCode, addShaderDefines } from '../shader-code' import { ColorData } from 'mol-geo/util/color-data'; @@ -18,8 +18,6 @@ type Point = 'point' namespace Point { export type Props = { - objectId: number - position: ValueCell<Float32Array> id: ValueCell<Float32Array> @@ -32,7 +30,7 @@ namespace Point { positionCount: number, usePointSizeAttenuation?: boolean - } + } & BaseProps export function create<T = Props>(ctx: Context, props: Props): Renderable<Props> { const defines = getBaseDefines(props) diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index e964f06f5194131034a9401b9e23cfd3e370d653..87504a7db07493a68c15c6503588c0c8fea63493 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -65,9 +65,10 @@ interface BaseProps { instanceCount: number, elementCount: number, positionCount: number, + alpha: number, position: ValueCell<Float32Array> - normal?: ValueCell<Float32Array> + normal?: ValueCell<Float32Array | undefined> id: ValueCell<Float32Array> transform: ValueCell<Float32Array> @@ -87,6 +88,7 @@ export function getBaseUniformDefs(props: BaseProps) { // light_position: 'v3', light_color: 'v3', light_ambient: 'v3', + alpha: 'f', objectId: 'i', instanceCount: 'i', @@ -107,9 +109,9 @@ export function getBaseUniformDefs(props: BaseProps) { } export function getBaseUniformValues(props: BaseProps) { - const { objectId, instanceCount, elementCount } = props + const { objectId, instanceCount, elementCount, alpha } = props const uniformValues: UniformValues = { - objectId, instanceCount, elementCount + objectId, instanceCount, elementCount, alpha } const color = props.color if (color.type === 'instance' || color.type === 'element' || color.type === 'element-instance') { @@ -133,7 +135,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 +158,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/renderer.ts b/src/mol-gl/renderer.ts index 8fe4c2707afacaaf4762aa3cd6a2c227a1c51b0b..5aa260f1248e25744c7a3d32da4624bb3e20da4c 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -11,6 +11,8 @@ import { Camera } from 'mol-view/camera/base'; import Scene, { RenderObject } from './scene'; import { Context } from './webgl/context'; import { Mat4, Vec3 } from 'mol-math/linear-algebra'; +import { Renderable } from './renderable'; +import { Color } from 'mol-util/color'; export interface RendererStats { renderableCount: number @@ -29,6 +31,7 @@ interface Renderer { draw: () => void setViewport: (viewport: Viewport) => void + setClearColor: (color: Color) => void stats: RendererStats dispose: () => void @@ -38,47 +41,83 @@ function getPixelRatio() { return (typeof window !== 'undefined') ? window.devicePixelRatio : 1 } +export const DefaultRendererProps = { + clearColor: 0x000000 as Color, + viewport: Viewport.create(0, 0, 0, 0) +} +export type RendererProps = Partial<typeof DefaultRendererProps> + namespace Renderer { - export function create(ctx: Context, camera: Camera): Renderer { + export function create(ctx: Context, camera: Camera, props: RendererProps = {}): Renderer { const { gl } = ctx + let { clearColor, viewport: _viewport } = { ...DefaultRendererProps, ...props } const scene = Scene.create(ctx) const model = Mat4.identity() - const viewport = Viewport.create(0, 0, 0, 0) + const viewport = Viewport.clone(_viewport) const pixelRatio = getPixelRatio() // const light_position = Vec3.create(0, 0, -100) const light_color = Vec3.create(1.0, 1.0, 1.0) const light_ambient = Vec3.create(0.5, 0.5, 0.5) + function setClearColor(color: Color) { + const [ r, g, b ] = Color.toRgbNormalized(color) + gl.clearColor(r, g, b, 1.0) + } + setClearColor(clearColor) + + let currentProgramId = -1 + const drawObject = (r: Renderable<any>, o: RenderObject) => { + if (o.props.visible) { + if (currentProgramId !== r.program.id) { + if (o.props.doubleSided) { + gl.disable(gl.CULL_FACE) + } else { + gl.enable(gl.CULL_FACE) + } + + if (o.props.flipSided) { + gl.frontFace(gl.CW) + gl.cullFace(gl.FRONT) + } else { + gl.frontFace(gl.CCW) + gl.cullFace(gl.BACK) + } + + r.program.use() + r.program.setUniforms({ + model, + view: camera.view, + projection: camera.projection, + + pixelRatio, + viewportHeight: viewport.height, + + // light_position, + light_color, + light_ambient, + }) + currentProgramId = r.program.id + } + r.draw() + } + } + const draw = () => { - // TODO clear color + currentProgramId = -1 + + gl.depthMask(true) gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + + gl.disable(gl.BLEND) gl.enable(gl.DEPTH_TEST) + scene.eachOpaque(drawObject) - // TODO painters sort, filter visible, filter picking, visibility culling? - let currentProgramId = -1 - scene.forEach((r, o) => { - if (o.visible) { - if (currentProgramId !== r.program.id) { - r.program.use() - r.program.setUniforms({ - model, - view: camera.view, - projection: camera.projection, - - pixelRatio, - viewportHeight: viewport.height, - - // light_position, - light_color, - light_ambient, - }) - currentProgramId = r.program.id - } - r.draw() - } - }) + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) + gl.enable(gl.BLEND) + gl.depthMask(false) + scene.eachTransparent(drawObject) } return { @@ -95,10 +134,13 @@ namespace Renderer { scene.clear() }, draw, + + setClearColor, setViewport: (newViewport: Viewport) => { Viewport.copy(viewport, newViewport) gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height) }, + get stats(): RendererStats { return { renderableCount: scene.count, diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 92faf5e27e14031434515bb3706acdad921d3d5d..5e7c91763c4f73f327bb51f8b35da1442da72c23 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -16,16 +16,16 @@ function getNextId() { export type RenderData = { [k: string]: ValueCell<Helpers.TypedArray> } -export interface BaseRenderObject { id: number, type: string, props: {}, visible: boolean } +export interface BaseRenderObject { id: number, type: string, props: {} } export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', props: MeshRenderable.Props } export interface PointRenderObject extends BaseRenderObject { type: 'point', props: PointRenderable.Props } export type RenderObject = MeshRenderObject | PointRenderObject export function createMeshRenderObject(props: MeshRenderable.Props): MeshRenderObject { - return { id: getNextId(), type: 'mesh', props, visible: true } + return { id: getNextId(), type: 'mesh', props } } export function createPointRenderObject(props: PointRenderable.Props): PointRenderObject { - return { id: getNextId(), type: 'point', props, visible: true } + return { id: getNextId(), type: 'point', props } } export function createRenderable(ctx: Context, o: RenderObject) { @@ -40,6 +40,8 @@ interface Scene { remove: (o: RenderObject) => void clear: () => void forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void + eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void + eachTransparent: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void count: number } @@ -69,6 +71,16 @@ namespace Scene { forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => { renderableMap.forEach(callbackFn) }, + eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => { + renderableMap.forEach((r, o) => { + if (o.props.alpha === 1) callbackFn(r, o) + }) + }, + eachTransparent: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => { + renderableMap.forEach((r, o) => { + if (o.props.alpha < 1) callbackFn(r, o) + }) + }, get count() { return renderableMap.size } diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 0b51b0dc56720e08e4479b333e4ab83ac6eebb5a..ee2779c5d08eb83f0c5f9cead8e5187fdcdf6d7a 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' | 'FLIP_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..9b4fb18cfbb799bfd4a0a9c794c041bb8a5824b4 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/src/mol-gl/shader/mesh.frag @@ -4,14 +4,22 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +#ifdef FLAT_SHADED + #extension GL_OES_standard_derivatives : enable +#endif + precision highp float; // uniform vec3 light_position; uniform vec3 light_color; uniform vec3 light_ambient; uniform mat4 view; +uniform float alpha; -varying vec3 vNormal, vViewPosition; +#ifndef FLAT_SHADED + varying vec3 vNormal; +#endif +varying vec3 vViewPosition; #pragma glslify: import('./chunks/color-frag-params.glsl') @@ -35,7 +43,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; @@ -46,7 +65,8 @@ void main() { vec3 finalColor = material * (diffuse + ambient) + specular; // gl_FragColor.rgb = N; + // gl_FragColor.a = 1.0; // gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); gl_FragColor.rgb = finalColor; - gl_FragColor.a = 1.0; + gl_FragColor.a = alpha; } \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert index 4684fc84414be2c12b007111d9b6f6c303a090f9..27ac8270222f783511e8b4da379a839f59df1352 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,12 @@ 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))); + vec3 transformedNormal = normalize(normalMatrix * normalize(normal)); + #if defined(FLIP_SIDED) && !defined(DOUBLE_SIDED) // TODO checking DOUBLE_SIDED should not be required, ASR + transformedNormal = -transformedNormal; + #endif + vNormal = transformedNormal; + #endif } \ No newline at end of file diff --git a/src/mol-gl/shader/point.frag b/src/mol-gl/shader/point.frag index 7afba8f5e1baf326d903e6f898495ca201ef52fd..a39cf8d9866968633113ac50252b91cc2cfcc67e 100644 --- a/src/mol-gl/shader/point.frag +++ b/src/mol-gl/shader/point.frag @@ -6,9 +6,11 @@ precision highp float; +uniform float alpha; + #pragma glslify: import('./chunks/color-frag-params.glsl') void main(){ #pragma glslify: import('./chunks/color-assign-material.glsl') - gl_FragColor = vec4(material, 1.0); + gl_FragColor = vec4(material, alpha); } \ 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-math/linear-algebra/3d/mat3.ts b/src/mol-math/linear-algebra/3d/mat3.ts index 937403309d7dc9d94feb7cd60d102f1e23ddfb1d..cd577776b23b29e6abbd21049ae009ce42ce3a50 100644 --- a/src/mol-math/linear-algebra/3d/mat3.ts +++ b/src/mol-math/linear-algebra/3d/mat3.ts @@ -17,6 +17,8 @@ * furnished to do so, subject to the following conditions: */ +import { Mat4 } from '../3d' + interface Mat3 extends Array<number> { [d: number]: number, '@type': 'mat3', length: 9 } namespace Mat3 { @@ -27,6 +29,33 @@ namespace Mat3 { return ret as any; } + export function identity(): Mat3 { + const out = zero(); + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 1; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return out; + } + + export function setIdentity(mat: Mat3): Mat3 { + mat[0] = 1; + mat[1] = 0; + mat[2] = 0; + mat[3] = 0; + mat[4] = 1; + mat[5] = 0; + mat[6] = 0; + mat[7] = 0; + mat[8] = 1; + return mat; + } + export function toArray(a: Mat3, out: Helpers.NumberArray, offset: number) { out[offset + 0] = a[0]; out[offset + 1] = a[1]; @@ -37,9 +66,6 @@ namespace Mat3 { out[offset + 6] = a[6]; out[offset + 7] = a[7]; out[offset + 8] = a[8]; - out[offset + 9] = a[9]; - out[offset + 10] = a[10]; - out[offset + 11] = a[11]; } export function fromArray(a: Mat3, array: Helpers.NumberArray, offset: number) { @@ -52,11 +78,120 @@ namespace Mat3 { a[6] = array[offset + 6] a[7] = array[offset + 7] a[8] = array[offset + 8] - a[9] = array[offset + 9] - a[10] = array[offset + 10] - a[11] = array[offset + 11] return a } + + /** + * Copies the upper-left 3x3 values into the given mat3. + */ + export function fromMat4(out: Mat3, a: Mat4) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[4]; + out[4] = a[5]; + out[5] = a[6]; + out[6] = a[8]; + out[7] = a[9]; + out[8] = a[10]; + return out; + } + + /** + * Creates a new Mat3 initialized with values from an existing matrix + */ + export function clone(a: Mat3) { + return Mat3.copy(Mat3.zero(), a); + } + + /** + * Copy the values from one Mat3 to another + */ + export function copy(out: Mat3, a: Mat3) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + return out; + } + + /** + * Transpose the values of a Mat3 + */ + export function transpose(out: Mat3, a: Mat3) { + // If we are transposing ourselves we can skip a few steps but have to cache some values + if (out === a) { + const a01 = a[1], a02 = a[2], a12 = a[5]; + out[1] = a[3]; + out[2] = a[6]; + out[3] = a01; + out[5] = a[7]; + out[6] = a02; + out[7] = a12; + } else { + out[0] = a[0]; + out[1] = a[3]; + out[2] = a[6]; + out[3] = a[1]; + out[4] = a[4]; + out[5] = a[7]; + out[6] = a[2]; + out[7] = a[5]; + out[8] = a[8]; + } + return out; + } + + /** + * Inverts a Mat3 + */ + export function invert(out: Mat3, a: Mat3): Mat3 { + const a00 = a[0], a01 = a[1], a02 = a[2]; + const a10 = a[3], a11 = a[4], a12 = a[5]; + const a20 = a[6], a21 = a[7], a22 = a[8]; + + const b01 = a22 * a11 - a12 * a21; + const b11 = -a22 * a10 + a12 * a20; + const b21 = a21 * a10 - a11 * a20; + + // Calculate the determinant + let det = a00 * b01 + a01 * b11 + a02 * b21; + + if (!det) { + console.warn('non-invertible matrix.', a); + return out; + } + det = 1.0 / det; + + out[0] = b01 * det; + out[1] = (-a22 * a01 + a02 * a21) * det; + out[2] = (a12 * a01 - a02 * a11) * det; + out[3] = b11 * det; + out[4] = (a22 * a00 - a02 * a20) * det; + out[5] = (-a12 * a00 + a02 * a10) * det; + out[6] = b21 * det; + out[7] = (-a21 * a00 + a01 * a20) * det; + out[8] = (a11 * a00 - a01 * a10) * det; + return out; + } + + export function determinant(a: Mat3) { + const a00 = a[0], a01 = a[1], a02 = a[2]; + const a10 = a[3], a11 = a[4], a12 = a[5]; + const a20 = a[6], a21 = a[7], a22 = a[8]; + + const b01 = a22 * a11 - a12 * a21; + const b11 = -a22 * a10 + a12 * a20; + const b21 = a21 * a10 - a11 * a20; + + // Calculate the determinant + return a00 * b01 + a01 * b11 + a02 * b21; + } } export default Mat3 \ No newline at end of file diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index 7fb5b67350fe47e9c0019fc6a86293b579b81f27..bc44e7e6e7f9a41a855f179f6b8fd4b824c17dfc 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/src/mol-math/linear-algebra/3d/vec3.ts @@ -18,7 +18,7 @@ */ import Mat4 from './mat4'; -import { Quat } from '../3d'; +import { Quat, Mat3 } from '../3d'; interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 } @@ -265,7 +265,18 @@ namespace Vec3 { return out; } - /** Transforms the vec3 with a quat */ + /** + * Transforms the Vec3 with a Mat3. + */ + export function transformMat3(out: Vec3, a: Vec3, m: Mat3) { + const x = a[0], y = a[1], z = a[2]; + out[0] = x * m[0] + y * m[3] + z * m[6]; + out[1] = x * m[1] + y * m[4] + z * m[7]; + out[2] = x * m[2] + y * m[5] + z * m[8]; + return out; + } + + /** Transforms the Vec3 with a quat */ export function transformQuat(out: Vec3, a: Vec3, q: Quat) { // benchmarks: http://jsperf.com/quaternion-transform-vec3-implementations diff --git a/src/mol-math/linear-algebra/3d/vec4.ts b/src/mol-math/linear-algebra/3d/vec4.ts index f481cf99d7224a0356b14eb827934f4b635dbb2f..54e2cda220efba0d36d0eaed13867cd9f820c55c 100644 --- a/src/mol-math/linear-algebra/3d/vec4.ts +++ b/src/mol-math/linear-algebra/3d/vec4.ts @@ -62,6 +62,20 @@ namespace Vec4 { return a } + export function toVec3Array(a: Vec4, out: Helpers.NumberArray, offset: number) { + out[offset + 0] = a[0]; + out[offset + 1] = a[1]; + out[offset + 2] = a[2]; + } + + export function fromVec3Array(a: Vec4, array: Helpers.NumberArray, offset: number) { + a[0] = array[offset + 0] + a[1] = array[offset + 1] + a[2] = array[offset + 2] + a[3] = 0 + return a + } + export function copy(out: Vec4, a: Vec4) { out[0] = a[0]; out[1] = a[1]; diff --git a/src/mol-model/structure/query/properties.ts b/src/mol-model/structure/query/properties.ts index e9a8140747807b675984efe5d2ae5e1e096d729a..1381966083177d1216ba3603561979fbb20d1453 100644 --- a/src/mol-model/structure/query/properties.ts +++ b/src/mol-model/structure/query/properties.ts @@ -1,10 +1,11 @@ /** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> */ import { Element, Unit } from '../structure' +import { VdwRadius } from '../model/properties/atomic'; const constant = { true: Element.property(l => true), @@ -37,7 +38,10 @@ const atom = { label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_atom_id.value(l.element)), auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.auth_atom_id.value(l.element)), label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_alt_id.value(l.element)), - pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.pdbx_formal_charge.value(l.element)) + pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.pdbx_formal_charge.value(l.element)), + + // Derived + vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.hierarchy.atoms.type_symbol.value(l.element))), } const residue = { diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 0a0f42a580bf9e2b7774b8fc251b2db4b8d49a91..4e8d9c26230f39a4407a14e79c137811f6bb9cb2 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,13 +49,13 @@ 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) const camera = PerspectiveCamera.create({ - near: 0.01, + near: 0.1, far: 10000, position: Vec3.create(0, 0, 50) }) @@ -64,7 +64,12 @@ namespace Viewer { }) - const gl = getWebGLContext(canvas) + const gl = getWebGLContext(canvas, { + alpha: false, + antialias: true, + depth: true, + preserveDrawingBuffer: true + }) if (gl === null) { throw new Error('Could not create a WebGL rendering context') } @@ -99,16 +104,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) + if (renderObjectSet) renderObjectSet.forEach(o => o.props.visible = false) }, - show: (repr: StructureRepresentation) => { + show: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) - if (renderObjectSet) renderObjectSet.forEach(o => o.visible = true) + if (renderObjectSet) renderObjectSet.forEach(o => o.props.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 +125,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)) },