diff --git a/src/apps/canvas/app.ts b/src/apps/canvas/app.ts index 862b01b07cb334e986aa927e78d00197ecb2618f..f72add32be2129c55e97d5824848a92a4dd1b251 100644 --- a/src/apps/canvas/app.ts +++ b/src/apps/canvas/app.ts @@ -5,7 +5,7 @@ */ import Viewer from 'mol-view/viewer'; -import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile } from './util'; +import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile, getVolumeFromVolcif } from './util'; import { StructureView } from './structure-view'; import { BehaviorSubject } from 'rxjs'; import { CifBlock } from 'mol-io/reader/cif'; @@ -71,14 +71,14 @@ export class App { if (this.structureView) this.structureView.destroy(); const url = idOrUrl.length <= 4 ? `https://files.rcsb.org/download/${idOrUrl}.cif` : idOrUrl; const cif = await this.runTask(getCifFromUrl(url, options ? !!options.binary : false), 'Load mmCIF from URL') - this.loadMmcif(cif, options ? options.assemblyId : void 0) + this.loadMmcif(cif.blocks[0], options ? options.assemblyId : void 0) } async loadMmcifFile(file: File) { if (this.structureView) this.structureView.destroy(); const binary = /\.bcif$/.test(file.name); const cif = await this.runTask(getCifFromFile(file, binary), 'Load mmCIF from file') - this.loadMmcif(cif) + this.loadMmcif(cif.blocks[0]) } // @@ -100,4 +100,25 @@ export class App { const ccp4 = await this.runTask(getCcp4FromUrl(url), 'Load CCP4 from URL') this.loadCcp4(ccp4) } + + // + + async loadVolcif(cif: CifBlock) { + const volume = await this.runTask(getVolumeFromVolcif(cif), 'Get Volume') + this.volumeView = await this.runTask(VolumeView(this, this.viewer, volume), 'Init volume view') + this.volumeLoaded.next(this.volumeView) + } + + async loadVolcifFile(file: File) { + if (this.volumeView) this.volumeView.destroy(); + const binary = /\.bcif$/.test(file.name); + const cif = await this.runTask(getCifFromFile(file, binary), 'Load volCif from file') + this.loadVolcif(cif.blocks[1]) + } + + async loadVolcifUrl(url: string, binary?: boolean) { + if (this.volumeView) this.volumeView.destroy(); + const cif = await this.runTask(getCifFromUrl(url, binary), 'Load volCif from URL') + this.loadVolcif(cif.blocks[1]) + } } \ No newline at end of file diff --git a/src/apps/canvas/component/app.tsx b/src/apps/canvas/component/app.tsx index 7f2d390928f589244c393de613d88f7209456de3..5d5c388507d29024e39d6c2a2fdd098f792c316a 100644 --- a/src/apps/canvas/component/app.tsx +++ b/src/apps/canvas/component/app.tsx @@ -20,14 +20,16 @@ export interface AppProps { export interface AppState { structureView: StructureView | null, volumeView: VolumeView | null, - binary: boolean + mmcifBinary: boolean, + volcifBinary: boolean } export class AppComponent extends React.Component<AppProps, AppState> { state = { structureView: this.props.app.structureView, volumeView: this.props.app.volumeView, - binary: false + mmcifBinary: false, + volcifBinary: true } componentDidMount() { @@ -50,7 +52,7 @@ export class AppComponent extends React.Component<AppProps, AppState> { <div style={{width: '330px', paddingLeft: '10px', paddingRight: '10px', right: '0px', height: '100%', position: 'absolute', overflow: 'auto'}}> <div style={{marginTop: '10px'}}> <span>Load PDB ID or URL</span> - <input type='checkbox' checked={this.state.binary} onChange={e => this.setState({ binary: e.target.checked })} /> Binary<br /> + <input type='checkbox' checked={this.state.mmcifBinary} onChange={e => this.setState({ mmcifBinary: e.target.checked })} /> Binary<br /> <input style={{ width: '100%' }} type='text' @@ -58,7 +60,7 @@ export class AppComponent extends React.Component<AppProps, AppState> { if (e.keyCode === 13) { const value = e.currentTarget.value.trim() if (value) { - this.props.app.loadPdbIdOrMmcifUrl(value, { binary: this.state.binary }) + this.props.app.loadPdbIdOrMmcifUrl(value, { binary: this.state.mmcifBinary }) } } }} @@ -84,6 +86,22 @@ export class AppComponent extends React.Component<AppProps, AppState> { }} /> </div> + <div style={{marginTop: '10px'}}> + <span>Load DensityServer URL</span> + <input type='checkbox' checked={this.state.volcifBinary} onChange={e => this.setState({ volcifBinary: e.target.checked })} /> Binary<br /> + <input + style={{ width: '100%' }} + type='text' + onKeyDown={e => { + if (e.keyCode === 13) { + const value = e.currentTarget.value.trim() + if (value) { + this.props.app.loadVolcifUrl(value, this.state.volcifBinary) + } + } + }} + /> + </div> <div> <span>Load example </span> <select diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts index 45b617685d07779fb6dc3fac469d0117e0396319..c848f987e08498d97caee37b5c533be760e3c230 100644 --- a/src/apps/canvas/index.ts +++ b/src/apps/canvas/index.ts @@ -23,4 +23,13 @@ const assemblyId = urlQueryParameter('assembly') const pdbId = urlQueryParameter('pdb') if (pdbId) app.loadPdbIdOrMmcifUrl(pdbId, { assemblyId }) -// app.loadCcp4Url('http://localhost:8091/ngl/data/betaGal.mrc') \ No newline at end of file +// app.loadCcp4Url('http://localhost:8091/ngl/data/betaGal.mrc') + +// app.loadPdbIdOrMmcifUrl('3pqr') +// app.loadVolCifUrl('https://webchem.ncbr.muni.cz/DensityServer/x-ray/3pqr/cell?space=fractional', true) + +// app.loadPdbIdOrMmcifUrl('5ire') +// app.loadVolcifUrl('https://webchem.ncbr.muni.cz/DensityServer/em/emd-8116/cell?space=cartesian&detail=6', true) + +// app.loadPdbIdOrMmcifUrl('5gag') +// app.loadVolcifUrl('https://webchem.ncbr.muni.cz/DensityServer/em/emd-8003/cell?detail=3', true) \ No newline at end of file diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts index 3b4658514a82a258c73edc8f7f15dacfef6b3dd1..dce28b08646960608f900b5a4ec2ba84edef3886 100644 --- a/src/apps/canvas/structure-view.ts +++ b/src/apps/canvas/structure-view.ts @@ -70,7 +70,7 @@ export async function StructureView(app: App, viewer: Viewer, models: ReadonlyAr const active: { [k: string]: boolean } = { cartoon: true, point: false, - surface: true, + surface: false, ballAndStick: false, carbohydrate: false, spacefill: false, diff --git a/src/apps/canvas/util.ts b/src/apps/canvas/util.ts index dcbc58697bd99d9897066ba0cd1c120bb9c6b27d..27581f68cbc2fdd956de7d61e04a65da7c21a92c 100644 --- a/src/apps/canvas/util.ts +++ b/src/apps/canvas/util.ts @@ -11,6 +11,7 @@ import CCP4 from 'mol-io/reader/ccp4/parser' import { FileHandle } from 'mol-io/common/file-handle'; import { Ccp4File } from 'mol-io/reader/ccp4/schema'; import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4'; +import { parseDensityServerData } from 'mol-model/volume'; // import { parse as parseObj } from 'mol-io/reader/obj/parser' // export async function getObjFromUrl(url: string) { @@ -25,7 +26,7 @@ export async function getCifFromData(data: string | Uint8Array) { const comp = CIF.parse(data) const parsed = await comp.run() if (parsed.isError) throw parsed - return parsed.result.blocks[0] + return parsed.result } export async function getCifFromUrl(url: string, binary = false) { @@ -68,4 +69,10 @@ export async function getCcp4FromData(data: Uint8Array) { export async function getVolumeFromCcp4(ccp4: Ccp4File) { return await volumeFromCcp4(ccp4).run() +} + +// + +export async function getVolumeFromVolcif(cif: CifBlock) { + return await parseDensityServerData(CIF.schema.densityServer(cif)).run() } \ No newline at end of file diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index cdfdccc303b35c54a3c064985ef842b5703264b1..76458adaa367d96f27246924f0b2fa11821fe7a3 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -7,17 +7,25 @@ import { RuntimeContext } from 'mol-task' import { ValueCell } from 'mol-util' import { Sphere3D, Box3D } from 'mol-math/geometry' -import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, TextParam } from 'mol-view/parameter'; -import { DirectVolume2dValues, DirectVolumeBaseValues, DirectVolume3dValues } from 'mol-gl/renderable/direct-volume'; -import { Vec3, Vec2, Mat4 } from 'mol-math/linear-algebra'; +import { paramDefaultValues, RangeParam, SelectParam, TextParam } from 'mol-view/parameter'; +import { DirectVolumeValues } from 'mol-gl/renderable/direct-volume'; +import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { Box } from '../../primitive/box'; import { getControlPointsFromString, createTransferFunctionTexture } from './transfer-function'; import { Texture } from 'mol-gl/webgl/texture'; +import { LocationIterator } from 'mol-geo/util/location-iterator'; +import { TransformData } from '../transform-data'; +import { createColors } from '../color-data'; +import { createMarkers } from '../marker-data'; +import { Geometry } from '../geometry'; const VolumeBox = Box() const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][] -interface DirectVolumeBase { +export interface DirectVolume { + readonly kind: 'direct-volume', + readonly gridTexture: ValueCell<Texture>, + readonly gridTextureDim: ValueCell<Vec3>, readonly gridDimension: ValueCell<Vec3>, readonly bboxSize: ValueCell<Vec3> readonly bboxMin: ValueCell<Vec3> @@ -28,74 +36,12 @@ interface DirectVolumeBase { boundingSphere?: Sphere3D } -const BaseParams = { - alpha: RangeParam('Opacity', '', 1, 0, 1, 0.01), - visible: BooleanParam('Visible', '', true), - depthMask: BooleanParam('Depth Mask', '', true), - useFog: BooleanParam('Use Fog', '', false), - isoValueAbsolute: RangeParam('Iso Value Absolute', '', 0.22, -1, 1, 0.01), - isoValueRelative: RangeParam('Iso Value Relative', '', 2, -10, 10, 0.1), - renderMode: SelectParam('Render Mode', '', 'volume', RenderModeOptions), - controlPoints: TextParam('Control Points', '', '0.19:0.1, 0.2:0.5, 0.21:0.1, 0.4:0.3'), -} -const DefaultBaseProps = paramDefaultValues(BaseParams) -type BaseProps = typeof DefaultBaseProps - -async function createBaseValues(ctx: RuntimeContext, directVolume: DirectVolumeBase, props: BaseProps): Promise<DirectVolumeBaseValues> { - const { bboxSize, bboxMin, bboxMax, gridDimension, transform } = directVolume - - const controlPoints = getControlPointsFromString(props.controlPoints) - const transferTex = createTransferFunctionTexture(controlPoints) - - const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 - console.log('maxSteps', maxSteps) - - return { - drawCount: ValueCell.create(VolumeBox.indices.length), - instanceCount: ValueCell.create(1), - - aPosition: ValueCell.create(VolumeBox.vertices as Float32Array), - elements: ValueCell.create(VolumeBox.indices as Uint32Array), - - uAlpha: ValueCell.create(props.alpha), - dUseFog: ValueCell.create(props.useFog), - - uIsoValue: ValueCell.create(props.isoValueAbsolute), - uBboxMin: bboxMin, - uBboxMax: bboxMax, - uBboxSize: bboxSize, - dMaxSteps: ValueCell.create(maxSteps), - uTransform: transform, - uGridDim: gridDimension, - dRenderMode: ValueCell.create(props.renderMode), - tTransferTex: transferTex, - } -} - -function updateBaseValues(values: DirectVolumeBaseValues, props: BaseProps) { - console.log('DirectVolumeBaseValues', props, values) - ValueCell.updateIfChanged(values.uIsoValue, props.isoValueAbsolute) - ValueCell.updateIfChanged(values.uAlpha, props.alpha) - ValueCell.updateIfChanged(values.dUseFog, props.useFog) - ValueCell.updateIfChanged(values.dRenderMode, props.renderMode) - - const controlPoints = getControlPointsFromString(props.controlPoints) - createTransferFunctionTexture(controlPoints, values.tTransferTex) -} - -// 2d - -export interface DirectVolume2d extends DirectVolumeBase { - readonly kind: 'direct-volume-2d', - readonly gridTexture: ValueCell<Texture>, - readonly gridTextureDim: ValueCell<Vec2>, -} - -export namespace DirectVolume2d { - export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume2d): DirectVolume2d { +export namespace DirectVolume { + export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume): DirectVolume { + const { width, height, depth } = texture if (directVolume) { ValueCell.update(directVolume.gridDimension, gridDimension) - ValueCell.update(directVolume.gridTextureDim, Vec2.set(directVolume.gridTextureDim.ref.value, texture.width, texture.height)) + ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth)) ValueCell.update(directVolume.bboxMin, bbox.min) ValueCell.update(directVolume.bboxMax, bbox.max) ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min)) @@ -103,10 +49,10 @@ export namespace DirectVolume2d { return directVolume } else { return { - kind: 'direct-volume-2d' as 'direct-volume-2d', + kind: 'direct-volume', gridDimension: ValueCell.create(gridDimension), gridTexture: ValueCell.create(texture), - gridTextureDim: ValueCell.create(Vec2.create(texture.width, texture.height)), + gridTextureDim: ValueCell.create(Vec3.create(width, height, depth)), bboxMin: ValueCell.create(bbox.min), bboxMax: ValueCell.create(bbox.max), bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)), @@ -115,78 +61,68 @@ export namespace DirectVolume2d { } } - export function createEmpty(directVolume?: DirectVolume2d): DirectVolume2d { - return {} as DirectVolume2d // TODO + export function createEmpty(directVolume?: DirectVolume): DirectVolume { + return {} as DirectVolume // TODO } - export const Params = BaseParams + export const Params = { + ...Geometry.Params, + isoValueAbsolute: RangeParam('Iso Value Absolute', '', 0.22, -1, 1, 0.01), + isoValueRelative: RangeParam('Iso Value Relative', '', 2, -10, 10, 0.1), + renderMode: SelectParam('Render Mode', '', 'isosurface', RenderModeOptions), + controlPoints: TextParam('Control Points', '', '0.19:0.1, 0.2:0.5, 0.21:0.1, 0.4:0.3'), + } export const DefaultProps = paramDefaultValues(Params) export type Props = typeof DefaultProps - export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume2d, props: Props): Promise<DirectVolume2dValues> { + export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<DirectVolumeValues> { const { gridTexture, gridTextureDim } = directVolume - return { - ...await createBaseValues(ctx, directVolume, props), - dGridTexType: ValueCell.create('2d'), - uGridTexDim: gridTextureDim, - tGridTex: gridTexture, - } - } + const { instanceCount, groupCount } = locationIt + const color = await createColors(ctx, locationIt, props) + const marker = createMarkers(instanceCount * groupCount) - export function updateValues(values: DirectVolume2dValues, props: Props) { - updateBaseValues(values, props) - } -} + const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount } -// 3d + const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume -export interface DirectVolume3d extends DirectVolumeBase { - readonly kind: 'direct-volume-3d', - readonly gridTexture: ValueCell<Texture>, -} + const controlPoints = getControlPointsFromString(props.controlPoints) + const transferTex = createTransferFunctionTexture(controlPoints) -export namespace DirectVolume3d { - export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume3d): DirectVolume3d { - if (directVolume) { - ValueCell.update(directVolume.gridDimension, gridDimension) - ValueCell.update(directVolume.bboxMin, bbox.min) - ValueCell.update(directVolume.bboxMax, bbox.max) - ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min)) - ValueCell.update(directVolume.transform, transform) - return directVolume - } else { - return { - kind: 'direct-volume-3d' as 'direct-volume-3d', - gridDimension: ValueCell.create(gridDimension), - gridTexture: ValueCell.create(texture), - bboxMin: ValueCell.create(bbox.min), - bboxMax: ValueCell.create(bbox.max), - bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)), - transform: ValueCell.create(transform), - } - } - } - - export function createEmpty(directVolume?: DirectVolume3d): DirectVolume3d { - return {} as DirectVolume3d // TODO - } - - export const Params = BaseParams - export const DefaultProps = paramDefaultValues(Params) - export type Props = typeof DefaultProps - - export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume3d, props: Props): Promise<DirectVolume3dValues> { - const { gridTexture } = directVolume + const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 return { - ...await createBaseValues(ctx, directVolume, props), - dGridTexType: ValueCell.create('3d'), + ...color, + ...marker, + ...transform, + ...Geometry.createValues(props, counts), + + aPosition: ValueCell.create(VolumeBox.vertices as Float32Array), + elements: ValueCell.create(VolumeBox.indices as Uint32Array), + + uIsoValue: ValueCell.create(props.isoValueAbsolute), + uBboxMin: bboxMin, + uBboxMax: bboxMax, + uBboxSize: bboxSize, + dMaxSteps: ValueCell.create(maxSteps), + uTransform: gridTransform, + uGridDim: gridDimension, + dRenderMode: ValueCell.create(props.renderMode), + tTransferTex: transferTex, + + dGridTexType: ValueCell.create(gridTexture.ref.value.depth > 0 ? '3d' : '2d'), + uGridTexDim: gridTextureDim, tGridTex: gridTexture, } } - export function updateValues(values: DirectVolume3dValues, props: Props) { - updateBaseValues(values, props) + export function updateValues(values: DirectVolumeValues, props: Props) { + ValueCell.updateIfChanged(values.uIsoValue, props.isoValueAbsolute) + ValueCell.updateIfChanged(values.uAlpha, props.alpha) + ValueCell.updateIfChanged(values.dUseFog, props.useFog) + ValueCell.updateIfChanged(values.dRenderMode, props.renderMode) + + const controlPoints = getControlPointsFromString(props.controlPoints) + createTransferFunctionTexture(controlPoints, values.tTransferTex) } } \ No newline at end of file diff --git a/src/mol-geo/geometry/geometry.ts b/src/mol-geo/geometry/geometry.ts index b8920b17a75cd1b065fe7d909d9cf592c6a955d9..d9e7e2f0d212972df39a0fc4638fa956a40b58e3 100644 --- a/src/mol-geo/geometry/geometry.ts +++ b/src/mol-geo/geometry/geometry.ts @@ -17,7 +17,7 @@ import { SizeType } from './size-data'; import { Lines } from './lines/lines'; import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, ColorParam, StructureParam, ValueParam } from 'mol-view/parameter' import { Structure } from 'mol-model/structure'; -import { DirectVolume2d, DirectVolume3d } from './direct-volume/direct-volume'; +import { DirectVolume } from './direct-volume/direct-volume'; import { Context } from 'mol-gl/webgl/context'; // @@ -43,8 +43,7 @@ export type GeometryKindType = { 'mesh': Mesh, 'points': Points, 'lines': Lines, - 'direct-volume-2d': DirectVolume2d, - 'direct-volume-3d': DirectVolume3d + 'direct-volume': DirectVolume, } export type GeometryKind = keyof GeometryKindType export type Geometry = Helpers.ValueOf<GeometryKindType> @@ -55,8 +54,7 @@ export namespace Geometry { case 'mesh': return geometry.triangleCount * 3 case 'points': return geometry.pointCount case 'lines': return geometry.lineCount * 2 * 3 - case 'direct-volume-2d': return 12 * 3 - case 'direct-volume-3d': return 12 * 3 + case 'direct-volume': return 12 * 3 } } diff --git a/src/mol-geo/geometry/mesh/mesh.ts b/src/mol-geo/geometry/mesh/mesh.ts index e87de9bf285eb9bc12a7c581d1ba1e3ebc3a1425..ad4cec0ba85172c3fb8b961543413b8248132f2b 100644 --- a/src/mol-geo/geometry/mesh/mesh.ts +++ b/src/mol-geo/geometry/mesh/mesh.ts @@ -103,6 +103,28 @@ export namespace Mesh { mesh.normalsComputed = true; } + export function checkForDuplicateVertices(mesh: Mesh, fractionDigits = 3) { + const v = mesh.vertexBuffer.ref.value + + const map = new Map<string, number>() + const hash = (v: Vec3, d: number) => `${v[0].toFixed(d)}|${v[1].toFixed(d)}|${v[2].toFixed(d)}` + let duplicates = 0 + + const a = Vec3.zero() + for (let i = 0, il = mesh.vertexCount; i < il; ++i) { + Vec3.fromArray(a, v, i * 3) + const k = hash(a, fractionDigits) + const count = map.get(k) + if (count !== undefined) { + duplicates += 1 + map.set(k, count + 1) + } else { + map.set(k, 1) + } + } + return duplicates + } + export function computeNormals(surface: Mesh): Task<Mesh> { return Task.create<Mesh>('Surface (Compute Normals)', async ctx => { if (surface.normalsComputed) return surface; diff --git a/src/mol-geo/geometry/picking.ts b/src/mol-geo/geometry/picking.ts index 07f2bf743ea1d5b18717a002910ce1a246684cb0..ac145f4b1907658d6a512d5d5440ec25fbae8619 100644 --- a/src/mol-geo/geometry/picking.ts +++ b/src/mol-geo/geometry/picking.ts @@ -11,7 +11,7 @@ function decodeFloatRGBA(r: number, g: number, b: number) { return r * 256 * 256 + g * 256 + b } -export function decodeIdRGBA(r: number, g: number, b: number) { +export function decodeIdRGB(r: number, g: number, b: number) { return decodeFloatRGBA(r, g, b) - 1 } diff --git a/src/mol-geo/representation/structure/complex-visual.ts b/src/mol-geo/representation/structure/complex-visual.ts index 72e36694c3d5daeb207207562f92796ba6baaaaf..02bfb79588f31013725f319de8d95486d6e7b5b9 100644 --- a/src/mol-geo/representation/structure/complex-visual.ts +++ b/src/mol-geo/representation/structure/complex-visual.ts @@ -6,47 +6,57 @@ import { Structure } from 'mol-model/structure'; import { Visual } from '..'; -import { MeshRenderObject } from 'mol-gl/render-object'; +import { MeshRenderObject, LinesRenderObject, PointsRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object'; import { Mesh } from '../../geometry/mesh/mesh'; import { RuntimeContext } from 'mol-task'; import { LocationIterator } from '../../util/location-iterator'; -import { createComplexMeshRenderObject } from './visual/util/common'; -import { StructureProps, VisualUpdateState, StructureMeshParams } from '.'; +import { createComplexMeshRenderObject, sizeChanged, colorChanged, UnitKind, UnitKindOptions } from './visual/util/common'; +import { StructureProps, VisualUpdateState, StructureMeshParams, StructureParams } from '.'; import { deepEqual, ValueCell } from 'mol-util'; import { PickingId } from '../../geometry/picking'; import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci'; import { MarkerAction, applyMarkerAction } from '../../geometry/marker-data'; import { Interval } from 'mol-data/int'; -import { updateRenderableState } from '../../geometry/geometry'; +import { updateRenderableState, Geometry } from '../../geometry/geometry'; import { createColors } from '../../geometry/color-data'; -import { UnitKindOptions, UnitKind } from './units-visual'; import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter'; +import { RenderableValues } from 'mol-gl/renderable/schema'; +import { createSizes } from 'mol-geo/geometry/size-data'; export interface ComplexVisual<P extends StructureProps> extends Visual<Structure, P> { } -export const ComplexMeshParams = { - ...StructureMeshParams, - unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions), +const ComplexParams = { + ...StructureParams, + unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions), } -export const DefaultComplexMeshProps = paramDefaultValues(ComplexMeshParams) -export type ComplexMeshProps = typeof DefaultComplexMeshProps +const DefaultComplexProps = paramDefaultValues(ComplexParams) +type ComplexProps = typeof DefaultComplexProps + +type ComplexRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject -export interface ComplexMeshVisualBuilder<P extends ComplexMeshProps> { +interface ComplexVisualBuilder<P extends ComplexProps, G extends Geometry> { defaultProps: P - createMesh(ctx: RuntimeContext, structure: Structure, props: P, mesh?: Mesh): Promise<Mesh> + createGeometry(ctx: RuntimeContext, structure: Structure, props: P, geometry?: G): Promise<G> createLocationIterator(structure: Structure): LocationIterator getLoci(pickingId: PickingId, structure: Structure, id: number): Loci mark(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean): boolean, setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void } -export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMeshVisualBuilder<P>): ComplexVisual<P> { - const { defaultProps, createMesh, createLocationIterator, getLoci, mark, setUpdateState } = builder +interface ComplexVisualGeometryBuilder<P extends ComplexProps, G extends Geometry> extends ComplexVisualBuilder<P, G> { + createEmptyGeometry(geometry?: G): G + createRenderObject(ctx: RuntimeContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, currentProps: P): Promise<ComplexRenderObject> + updateValues(values: RenderableValues, newProps: P): void +} + +export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisualGeometryBuilder<P, Geometry>): ComplexVisual<P> { + const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder + const { createRenderObject, updateValues } = builder const updateState = VisualUpdateState.create() - let renderObject: MeshRenderObject | undefined + let renderObject: ComplexRenderObject | undefined let currentProps: P - let mesh: Mesh + let geometry: Geometry let currentStructure: Structure let locationIt: LocationIterator let conformationHash: number @@ -56,10 +66,10 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe currentStructure = structure conformationHash = Structure.conformationHash(currentStructure) - mesh = await createMesh(ctx, currentStructure, currentProps, mesh) + geometry = await createGeometry(ctx, currentStructure, currentProps, geometry) locationIt = createLocationIterator(structure) - renderObject = await createComplexMeshRenderObject(ctx, structure, mesh, locationIt, currentProps) + renderObject = await createRenderObject(ctx, structure, geometry, locationIt, currentProps) } async function update(ctx: RuntimeContext, props: Partial<P>) { @@ -77,25 +87,30 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe updateState.createGeometry = true } - if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.createGeometry = true - if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true - // if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createMesh = true // TODO + if (colorChanged(currentProps, newProps)) updateState.updateColor = true + if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true // if (updateState.createGeometry) { - mesh = await createMesh(ctx, currentStructure, newProps, mesh) - ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) + geometry = await createGeometry(ctx, currentStructure, newProps, geometry) + ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry)) updateState.updateColor = true } + if (updateState.updateSize) { + // not all geometries have size data, so check here + if ('uSize' in renderObject.values) { + await createSizes(ctx, locationIt, newProps, renderObject.values) + } + } + if (updateState.updateColor) { await createColors(ctx, locationIt, newProps, renderObject.values) } - // TODO why do I need to cast here? - Mesh.updateValues(renderObject.values, newProps as ComplexMeshProps) - updateRenderableState(renderObject.state, newProps as ComplexMeshProps) + updateValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) currentProps = newProps return true @@ -147,4 +162,28 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe renderObject = undefined } } +} + +// mesh + +export const ComplexMeshParams = { + ...StructureMeshParams, + unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions), +} +export const DefaultComplexMeshProps = paramDefaultValues(ComplexMeshParams) +export type ComplexMeshProps = typeof DefaultComplexMeshProps + +export interface ComplexMeshVisualBuilder<P extends ComplexMeshProps> extends ComplexVisualBuilder<P, Mesh> { } + +export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMeshVisualBuilder<P>): ComplexVisual<P> { + return ComplexVisual({ + ...builder, + setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => { + builder.setUpdateState(state, newProps, currentProps) + if (sizeChanged(currentProps, newProps)) state.createGeometry = true + }, + createEmptyGeometry: Mesh.createEmpty, + createRenderObject: createComplexMeshRenderObject, + updateValues: Mesh.updateValues + }) } \ 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 f1b8d592bac06a5b8e403e4b82be4e2410c53467..b5c094fc2efb84ec161a72296e1d7015d7076a16 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -14,7 +14,7 @@ import { Mesh } from '../../geometry/mesh/mesh'; import { Points } from '../../geometry/points/points'; import { Lines } from '../../geometry/lines/lines'; import { SelectParam, paramDefaultValues } from 'mol-view/parameter'; -import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume'; +import { DirectVolume } from '../../geometry/direct-volume/direct-volume'; export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { } @@ -48,8 +48,7 @@ export const DefaultStructureLinesProps = paramDefaultValues(StructureLinesParam export type StructureLinesProps = typeof DefaultStructureLinesProps export const StructureDirectVolumeParams = { - ...DirectVolume2d.Params, - ...DirectVolume3d.Params, + ...DirectVolume.Params, ...StructureParams, } export const DefaultStructureDirectVolumeProps = paramDefaultValues(StructureDirectVolumeParams) diff --git a/src/mol-geo/representation/structure/representation/ball-and-stick.ts b/src/mol-geo/representation/structure/representation/ball-and-stick.ts index b4cddb856e24903fa6820df0849c7f8ae33f39ff..90eec78fea53099686fdd74a0d436b890df4ea6e 100644 --- a/src/mol-geo/representation/structure/representation/ball-and-stick.ts +++ b/src/mol-geo/representation/structure/representation/ball-and-stick.ts @@ -16,7 +16,7 @@ import { InterUnitLinkVisual } from '../visual/inter-unit-link-cylinder'; import { SizeThemeName, SizeThemeOptions } from 'mol-view/theme/size'; import { getQualityProps } from '../../util'; import { paramDefaultValues, SelectParam, NumberParam, MultiSelectParam } from 'mol-view/parameter'; -import { UnitKind, UnitKindOptions } from '../units-visual'; +import { UnitKind, UnitKindOptions } from '../visual/util/common'; export const BallAndStickParams = { ...ElementSphereParams, diff --git a/src/mol-geo/representation/structure/representation/molecular-surface.ts b/src/mol-geo/representation/structure/representation/molecular-surface.ts index e9546a1e5439e097bb698bb29ee8d0ab9228efaf..be412fc1ea8096999ffcee1aa20601da73984709 100644 --- a/src/mol-geo/representation/structure/representation/molecular-surface.ts +++ b/src/mol-geo/representation/structure/representation/molecular-surface.ts @@ -9,7 +9,7 @@ import { GaussianSurfaceVisual, GaussianSurfaceParams } from '../visual/gaussian import { StructureRepresentation } from '../units-representation'; import { Structure } from 'mol-model/structure'; import { MarkerAction } from '../../../geometry/marker-data'; -import { Loci } from 'mol-model/loci'; +import { Loci, isEmptyLoci } from 'mol-model/loci'; import { PickingId } from '../../../geometry/picking'; import { Task } from 'mol-task'; import { GaussianWireframeVisual, GaussianWireframeParams } from '../visual/gaussian-surface-wireframe'; @@ -63,10 +63,24 @@ export function MolecularSurfaceRepresentation(): MolecularSurfaceRepresentation }) }, getLoci: (pickingId: PickingId) => { - return gaussianSurfaceRepr.getLoci(pickingId) + const surfaceLoci = gaussianSurfaceRepr.getLoci(pickingId) + const wireframeLoci = gaussianWireframeRepr.getLoci(pickingId) + const volumeLoci = gaussianVolumeRepr.getLoci(pickingId) + if (isEmptyLoci(surfaceLoci)) { + if (isEmptyLoci(wireframeLoci)) { + return volumeLoci + } else { + return wireframeLoci + } + } else { + return surfaceLoci + } }, mark: (loci: Loci, action: MarkerAction) => { - return gaussianSurfaceRepr.mark(loci, action) + const markSurfaceElement = gaussianSurfaceRepr.mark(loci, action) + const markWireframeElement = gaussianWireframeRepr.mark(loci, action) + const markVolumeElement = gaussianVolumeRepr.mark(loci, action) + return markSurfaceElement || markWireframeElement || markVolumeElement }, destroy() { gaussianSurfaceRepr.destroy() diff --git a/src/mol-geo/representation/structure/units-visual.ts b/src/mol-geo/representation/structure/units-visual.ts index 353404ea7f7cca159122fc3810b2ffa0c4789054..5e674479a1c2086eeb4475ca6b622c55282519e8 100644 --- a/src/mol-geo/representation/structure/units-visual.ts +++ b/src/mol-geo/representation/structure/units-visual.ts @@ -4,37 +4,27 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -// TODO refactor to make DRY - import { Unit, Structure } from 'mol-model/structure'; import { RepresentationProps, Visual } from '../'; -import { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams, StructureProps } from '.'; +import { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams, StructureParams } from '.'; import { RuntimeContext } from 'mol-task'; import { PickingId } from '../../geometry/picking'; import { LocationIterator } from '../../util/location-iterator'; import { Mesh } from '../../geometry/mesh/mesh'; import { MarkerAction, applyMarkerAction, createMarkers } from '../../geometry/marker-data'; import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci'; -import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolume2dRenderObject, DirectVolume3dRenderObject } from 'mol-gl/render-object'; -import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject } from './visual/util/common'; +import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object'; +import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject, UnitKind, UnitKindOptions, includesUnitKind, colorChanged, sizeChanged } from './visual/util/common'; import { deepEqual, ValueCell, UUID } from 'mol-util'; import { Interval } from 'mol-data/int'; import { Points } from '../../geometry/points/points'; import { updateRenderableState, Geometry } from '../../geometry/geometry'; -import { createColors, ColorProps } from '../../geometry/color-data'; -import { createSizes, SizeProps } from '../../geometry/size-data'; +import { createColors } from '../../geometry/color-data'; +import { createSizes } from '../../geometry/size-data'; import { Lines } from '../../geometry/lines/lines'; import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter'; -import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume'; - -export const UnitKindInfo = { - 'atomic': {}, - 'spheres': {}, - 'gaussians': {}, -} -export type UnitKind = keyof typeof UnitKindInfo -export const UnitKindNames = Object.keys(UnitKindInfo) -export const UnitKindOptions = UnitKindNames.map(n => [n, n] as [UnitKind, string]) +import { DirectVolume } from '../../geometry/direct-volume/direct-volume'; +import { RenderableValues } from 'mol-gl/renderable/schema'; export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup } @@ -47,35 +37,16 @@ function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.Symmetry ) } -function includesUnitKind(unitKinds: UnitKind[], unit: Unit) { - for (let i = 0, il = unitKinds.length; i < il; ++i) { - if (Unit.isAtomic(unit) && unitKinds[i] === 'atomic') return true - if (Unit.isSpheres(unit) && unitKinds[i] === 'spheres') return true - if (Unit.isGaussians(unit) && unitKinds[i] === 'gaussians') return true - } - return false -} - -function sizeChanged(oldProps: SizeProps, newProps: SizeProps) { - return ( - oldProps.sizeTheme !== newProps.sizeTheme || - oldProps.sizeValue !== newProps.sizeValue || - oldProps.sizeFactor !== newProps.sizeFactor - ) -} - -function colorChanged(oldProps: ColorProps, newProps: ColorProps) { - return ( - oldProps.colorTheme !== newProps.colorTheme || - oldProps.colorValue !== newProps.colorValue - ) -} - const UnitsParams = { + ...StructureParams, unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions), } +const DefaultUnitsProps = paramDefaultValues(UnitsParams) +type UnitsProps = typeof DefaultUnitsProps + +type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject -interface UnitsVisualBuilder<P extends StructureProps, G extends Geometry> { +interface UnitsVisualBuilder<P extends UnitsProps, G extends Geometry> { defaultProps: P createGeometry(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, geometry?: G): Promise<G> createLocationIterator(group: Unit.SymmetryGroup): LocationIterator @@ -84,23 +55,20 @@ interface UnitsVisualBuilder<P extends StructureProps, G extends Geometry> { setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void } -// mesh - -export const UnitsMeshParams = { - ...StructureMeshParams, - ...UnitsParams, +interface UnitsVisualGeometryBuilder<P extends UnitsProps, G extends Geometry> extends UnitsVisualBuilder<P, G> { + createEmptyGeometry(geometry?: G): G + createRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, currentProps: P): Promise<UnitsRenderObject> + updateValues(values: RenderableValues, newProps: P): void } -export const DefaultUnitsMeshProps = paramDefaultValues(UnitsMeshParams) -export type UnitsMeshProps = typeof DefaultUnitsMeshProps -export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> extends UnitsVisualBuilder<P, Mesh> { } -export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> { +export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBuilder<P, Geometry>): UnitsVisual<P> { const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder + const { createEmptyGeometry, createRenderObject, updateValues } = builder const updateState = VisualUpdateState.create() - let renderObject: MeshRenderObject | undefined + let renderObject: UnitsRenderObject | undefined let currentProps: P - let mesh: Mesh + let geometry: Geometry let currentGroup: Unit.SymmetryGroup let currentStructure: Structure let locationIt: LocationIterator @@ -112,13 +80,13 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu const unit = group.units[0] currentConformationId = Unit.conformationId(unit) - mesh = includesUnitKind(currentProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, currentProps, mesh) - : Mesh.createEmpty(mesh) + geometry = includesUnitKind(currentProps.unitKinds, unit) + ? await createGeometry(ctx, unit, currentStructure, currentProps, geometry) + : createEmptyGeometry(geometry) // TODO create empty location iterator when not in unitKinds locationIt = createLocationIterator(group) - renderObject = await createUnitsMeshRenderObject(ctx, group, mesh, locationIt, currentProps) + renderObject = await createRenderObject(ctx, group, geometry, locationIt, currentProps) } async function update(ctx: RuntimeContext, props: Partial<P> = {}) { @@ -139,7 +107,6 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true - if (sizeChanged(currentProps, newProps)) updateState.createGeometry = true if (colorChanged(currentProps, newProps)) updateState.updateColor = true if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true @@ -154,20 +121,26 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu } if (updateState.createGeometry) { - mesh = includesUnitKind(newProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, newProps, mesh) - : Mesh.createEmpty(mesh) - ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) + geometry = includesUnitKind(newProps.unitKinds, unit) + ? await createGeometry(ctx, unit, currentStructure, newProps, geometry) + : createEmptyGeometry(geometry) + ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry)) updateState.updateColor = true } + if (updateState.updateSize) { + // not all geometries have size data, so check here + if ('uSize' in renderObject.values) { + await createSizes(ctx, locationIt, newProps, renderObject.values) + } + } + if (updateState.updateColor) { await createColors(ctx, locationIt, newProps, renderObject.values) } - // TODO why do I need to cast here? - Mesh.updateValues(renderObject.values, newProps as UnitsMeshProps) - updateRenderableState(renderObject.state, newProps as UnitsMeshProps) + updateValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) currentProps = newProps } @@ -226,6 +199,29 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu } } +// mesh + +export const UnitsMeshParams = { + ...StructureMeshParams, + ...UnitsParams, +} +export const DefaultUnitsMeshProps = paramDefaultValues(UnitsMeshParams) +export type UnitsMeshProps = typeof DefaultUnitsMeshProps +export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> extends UnitsVisualBuilder<P, Mesh> { } + +export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> { + return UnitsVisual({ + ...builder, + setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => { + builder.setUpdateState(state, newProps, currentProps) + if (sizeChanged(currentProps, newProps)) state.createGeometry = true + }, + createEmptyGeometry: Mesh.createEmpty, + createRenderObject: createUnitsMeshRenderObject, + updateValues: Mesh.updateValues + }) +} + // points export const UnitsPointsParams = { @@ -237,139 +233,16 @@ export type UnitsPointsProps = typeof DefaultUnitsPointsProps export interface UnitsPointVisualBuilder<P extends UnitsPointsProps> extends UnitsVisualBuilder<P, Points> { } export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> { - const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder - const updateState = VisualUpdateState.create() - - let renderObject: PointsRenderObject | undefined - let currentProps: P - let points: Points - let currentGroup: Unit.SymmetryGroup - let currentStructure: Structure - let locationIt: LocationIterator - let currentConformationId: UUID - - async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) { - currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure }) - currentGroup = group - - const unit = group.units[0] - currentConformationId = Unit.conformationId(unit) - points = includesUnitKind(currentProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, currentProps, points) - : Points.createEmpty(points) - - // TODO create empty location iterator when not in unitKinds - locationIt = createLocationIterator(group) - renderObject = await createUnitsPointsRenderObject(ctx, group, points, locationIt, currentProps) - } - - async function update(ctx: RuntimeContext, props: Partial<P> = {}) { - if (!renderObject) return - - const newProps = Object.assign({}, currentProps, props, { structure: currentStructure }) - const unit = currentGroup.units[0] - - locationIt.reset() - VisualUpdateState.reset(updateState) - setUpdateState(updateState, newProps, currentProps) - - const newConformationId = Unit.conformationId(unit) - if (newConformationId !== currentConformationId) { - currentConformationId = newConformationId - updateState.createGeometry = true - } - - if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true - - if (sizeChanged(currentProps, newProps)) updateState.updateSize = true - if (colorChanged(currentProps, newProps)) updateState.updateColor = true - if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true - - // - - if (updateState.updateTransform) { - locationIt = createLocationIterator(currentGroup) - const { instanceCount, groupCount } = locationIt - createUnitsTransform(currentGroup, renderObject.values) - createMarkers(instanceCount * groupCount, renderObject.values) - updateState.updateColor = true - } - - if (updateState.createGeometry) { - points = includesUnitKind(newProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, newProps, points) - : Points.createEmpty(points) - ValueCell.update(renderObject.values.drawCount, points.pointCount) - updateState.updateColor = true - } - - if (updateState.updateSize) { - await createSizes(ctx, locationIt, newProps, renderObject.values) - } - - if (updateState.updateColor) { - await createColors(ctx, locationIt, newProps, renderObject.values) - } - - // TODO why do I need to cast here? - Points.updateValues(renderObject.values, newProps as UnitsPointsProps) - updateRenderableState(renderObject.state, newProps as UnitsPointsProps) - - currentProps = newProps - } - - return { - get renderObject () { return renderObject }, - async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) { - if (structureGroup) currentStructure = structureGroup.structure - const group = structureGroup ? structureGroup.group : undefined - if (!group && !currentGroup) { - throw new Error('missing group') - } else if (group && (!currentGroup || !renderObject)) { - // console.log('unit-visual first create') - await create(ctx, group, props) - } else if (group && group.hashCode !== currentGroup.hashCode) { - // console.log('unit-visual group.hashCode !== currentGroup.hashCode') - await create(ctx, group, props) - } else { - // console.log('unit-visual update') - if (group && !sameGroupConformation(group, currentGroup)) { - // console.log('unit-visual new conformation') - currentGroup = group - } - await update(ctx, props) - } - }, - getLoci(pickingId: PickingId) { - return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci + return UnitsVisual({ + ...builder, + createEmptyGeometry: Points.createEmpty, + createRenderObject: createUnitsPointsRenderObject, + setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => { + builder.setUpdateState(state, newProps, currentProps) + if (sizeChanged(currentProps, newProps)) state.updateSize = true }, - mark(loci: Loci, action: MarkerAction) { - if (!renderObject) return false - const { tMarker } = renderObject.values - const { groupCount, instanceCount } = locationIt - - function apply(interval: Interval) { - const start = Interval.start(interval) - const end = Interval.end(interval) - return applyMarkerAction(tMarker.ref.value.array, start, end, action) - } - - let changed = false - if (isEveryLoci(loci)) { - changed = apply(Interval.ofBounds(0, groupCount * instanceCount)) - } else { - changed = mark(loci, currentGroup, apply) - } - if (changed) { - ValueCell.update(tMarker, tMarker.ref.value) - } - return changed - }, - destroy() { - // TODO - renderObject = undefined - } - } + updateValues: Points.updateValues + }) } // lines @@ -383,139 +256,16 @@ export type UnitsLinesProps = typeof DefaultUnitsLinesProps export interface UnitsLinesVisualBuilder<P extends UnitsLinesProps> extends UnitsVisualBuilder<P, Lines> { } export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> { - const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder - const updateState = VisualUpdateState.create() - - let renderObject: LinesRenderObject | undefined - let currentProps: P - let lines: Lines - let currentGroup: Unit.SymmetryGroup - let currentStructure: Structure - let locationIt: LocationIterator - let currentConformationId: UUID - - async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) { - currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure }) - currentGroup = group - - const unit = group.units[0] - currentConformationId = Unit.conformationId(unit) - lines = includesUnitKind(currentProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, currentProps, lines) - : Lines.createEmpty(lines) - - // TODO create empty location iterator when not in unitKinds - locationIt = createLocationIterator(group) - renderObject = await createUnitsLinesRenderObject(ctx, group, lines, locationIt, currentProps) - } - - async function update(ctx: RuntimeContext, props: Partial<P> = {}) { - if (!renderObject) return - - const newProps = Object.assign({}, currentProps, props, { structure: currentStructure }) - const unit = currentGroup.units[0] - - locationIt.reset() - VisualUpdateState.reset(updateState) - setUpdateState(updateState, newProps, currentProps) - - const newConformationId = Unit.conformationId(unit) - if (newConformationId !== currentConformationId) { - currentConformationId = newConformationId - updateState.createGeometry = true - } - - if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true - - if (sizeChanged(currentProps, newProps)) updateState.updateSize = true - if (colorChanged(currentProps, newProps)) updateState.updateColor = true - if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true - - // - - if (updateState.updateTransform) { - locationIt = createLocationIterator(currentGroup) - const { instanceCount, groupCount } = locationIt - createUnitsTransform(currentGroup, renderObject.values) - createMarkers(instanceCount * groupCount, renderObject.values) - updateState.updateColor = true - } - - if (updateState.createGeometry) { - lines = includesUnitKind(newProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, newProps, lines) - : Lines.createEmpty(lines) - ValueCell.update(renderObject.values.drawCount, lines.lineCount * 2 * 3) - updateState.updateColor = true - } - - if (updateState.updateSize) { - await createSizes(ctx, locationIt, newProps, renderObject.values) - } - - if (updateState.updateColor) { - await createColors(ctx, locationIt, newProps, renderObject.values) - } - - // TODO why do I need to cast here? - Lines.updateValues(renderObject.values, newProps as UnitsLinesProps) - updateRenderableState(renderObject.state, newProps as UnitsLinesProps) - - currentProps = newProps - } - - return { - get renderObject () { return renderObject }, - async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) { - if (structureGroup) currentStructure = structureGroup.structure - const group = structureGroup ? structureGroup.group : undefined - if (!group && !currentGroup) { - throw new Error('missing group') - } else if (group && (!currentGroup || !renderObject)) { - // console.log('unit-visual first create') - await create(ctx, group, props) - } else if (group && group.hashCode !== currentGroup.hashCode) { - // console.log('unit-visual group.hashCode !== currentGroup.hashCode') - await create(ctx, group, props) - } else { - // console.log('unit-visual update') - if (group && !sameGroupConformation(group, currentGroup)) { - // console.log('unit-visual new conformation') - currentGroup = group - } - await update(ctx, props) - } - }, - getLoci(pickingId: PickingId) { - return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci - }, - mark(loci: Loci, action: MarkerAction) { - if (!renderObject) return false - const { tMarker } = renderObject.values - const { groupCount, instanceCount } = locationIt - - function apply(interval: Interval) { - const start = Interval.start(interval) - const end = Interval.end(interval) - return applyMarkerAction(tMarker.ref.value.array, start, end, action) - } - - let changed = false - if (isEveryLoci(loci)) { - changed = apply(Interval.ofBounds(0, groupCount * instanceCount)) - } else { - changed = mark(loci, currentGroup, apply) - } - if (changed) { - ValueCell.update(tMarker, tMarker.ref.value) - } - return changed + return UnitsVisual({ + ...builder, + createEmptyGeometry: Lines.createEmpty, + createRenderObject: createUnitsLinesRenderObject, + setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => { + builder.setUpdateState(state, newProps, currentProps) + if (sizeChanged(currentProps, newProps)) state.updateSize = true }, - destroy() { - // TODO - renderObject = undefined - } - } + updateValues: Lines.updateValues + }) } // direct-volume @@ -526,132 +276,17 @@ export const UnitsDirectVolumeParams = { } export const DefaultUnitsDirectVolumeProps = paramDefaultValues(UnitsDirectVolumeParams) export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps -export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume2d | DirectVolume3d> { } +export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume> { } export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> { - const { defaultProps, createGeometry, createLocationIterator, getLoci, setUpdateState } = builder - const updateState = VisualUpdateState.create() - - let renderObject: DirectVolume2dRenderObject | DirectVolume3dRenderObject | undefined - let currentProps: P - let directVolume: DirectVolume2d | DirectVolume3d - let currentGroup: Unit.SymmetryGroup - let currentStructure: Structure - let locationIt: LocationIterator - let currentConformationId: UUID - - async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) { - const { webgl } = props - if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props') - - currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure }) - currentGroup = group - - const unit = group.units[0] - currentConformationId = Unit.conformationId(unit) - directVolume = includesUnitKind(currentProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, currentProps, directVolume) - : (webgl.isWebGL2 ? - DirectVolume2d.createEmpty(directVolume as DirectVolume2d) : - DirectVolume3d.createEmpty(directVolume as DirectVolume3d)) - - console.log('directVolume', directVolume) - - // TODO create empty location iterator when not in unitKinds - locationIt = createLocationIterator(group) - renderObject = await createUnitsDirectVolumeRenderObject(ctx, group, directVolume, locationIt, currentProps) - } - - async function update(ctx: RuntimeContext, props: Partial<P> = {}) { - const { webgl } = props - if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props') - - if (!renderObject) return - - const newProps = Object.assign({}, currentProps, props, { structure: currentStructure }) - const unit = currentGroup.units[0] - - locationIt.reset() - VisualUpdateState.reset(updateState) - setUpdateState(updateState, newProps, currentProps) - - const newConformationId = Unit.conformationId(unit) - if (newConformationId !== currentConformationId) { - currentConformationId = newConformationId - updateState.createGeometry = true - } - - if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true - - if (sizeChanged(currentProps, newProps)) updateState.createGeometry = true - if (colorChanged(currentProps, newProps)) updateState.updateColor = true - if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true - - // - - // if (updateState.updateTransform) { - // locationIt = createLocationIterator(currentGroup) - // const { instanceCount, groupCount } = locationIt - // createUnitsTransform(currentGroup, renderObject.values) - // createMarkers(instanceCount * groupCount, renderObject.values) - // updateState.updateColor = true - // } - - if (updateState.createGeometry) { - directVolume = includesUnitKind(newProps.unitKinds, unit) - ? await createGeometry(ctx, unit, currentStructure, newProps, directVolume) - : (webgl.isWebGL2 ? - DirectVolume2d.createEmpty(directVolume as DirectVolume2d) : - DirectVolume3d.createEmpty(directVolume as DirectVolume3d)) - updateState.updateColor = true - } - - // if (updateState.updateColor) { - // await createColors(ctx, locationIt, newProps, renderObject.values) - // } - - if (renderObject.type === 'direct-volume-2d') { - DirectVolume2d.updateValues(renderObject.values, newProps) - } else { - DirectVolume3d.updateValues(renderObject.values, newProps) - } - updateRenderableState(renderObject.state, newProps) - - currentProps = newProps - } - - return { - get renderObject () { return renderObject }, - async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) { - if (structureGroup) currentStructure = structureGroup.structure - const group = structureGroup ? structureGroup.group : undefined - if (!group && !currentGroup) { - throw new Error('missing group') - } else if (group && (!currentGroup || !renderObject)) { - // console.log('unit-visual first create') - await create(ctx, group, props) - } else if (group && group.hashCode !== currentGroup.hashCode) { - // console.log('unit-visual group.hashCode !== currentGroup.hashCode') - await create(ctx, group, props) - } else { - // console.log('unit-visual update') - if (group && !sameGroupConformation(group, currentGroup)) { - // console.log('unit-visual new conformation') - currentGroup = group - } - await update(ctx, props) - } + return UnitsVisual({ + ...builder, + createEmptyGeometry: DirectVolume.createEmpty, + createRenderObject: createUnitsDirectVolumeRenderObject, + setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => { + builder.setUpdateState(state, newProps, currentProps) + if (sizeChanged(currentProps, newProps)) state.createGeometry = true }, - getLoci(pickingId: PickingId) { - return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci - }, - mark(loci: Loci, action: MarkerAction) { - // TODO - return false - }, - destroy() { - // TODO - renderObject = undefined - } - } + updateValues: DirectVolume.updateValues + }) } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts index 926573e83282b2b8ce2cd7c6875d1112425b57cf..a475a28892756d2649c146e53d07c48103008cbf 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts @@ -74,7 +74,7 @@ export type CarbohydrateLinkProps = typeof DefaultCarbohydrateLinkProps export function CarbohydrateLinkVisual(): ComplexVisual<CarbohydrateLinkProps> { return ComplexMeshVisual<CarbohydrateLinkProps>({ defaultProps: DefaultCarbohydrateLinkProps, - createMesh: createCarbohydrateLinkCylinderMesh, + createGeometry: createCarbohydrateLinkCylinderMesh, createLocationIterator: CarbohydrateLinkIterator, getLoci: getLinkLoci, mark: markLink, diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts index 7d9dc5e03b54191575ccd2970db9ca7f62d9bcb6..15d4f0b765b0adb7d1cef23de5617697800a60b4 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -157,7 +157,7 @@ export type CarbohydrateSymbolProps = typeof DefaultCarbohydrateSymbolProps export function CarbohydrateSymbolVisual(): ComplexVisual<CarbohydrateSymbolProps> { return ComplexMeshVisual<CarbohydrateSymbolProps>({ defaultProps: DefaultCarbohydrateSymbolProps, - createMesh: createCarbohydrateSymbolMesh, + createGeometry: createCarbohydrateSymbolMesh, createLocationIterator: CarbohydrateElementIterator, getLoci: getCarbohydrateLoci, mark: markCarbohydrate, diff --git a/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts b/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts index 9b3070e05edd5de1302545dab388b455eff27b76..43764073b01327ad726ad6f9e333133bf6879c1d 100644 --- a/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts @@ -62,7 +62,7 @@ export type CrossLinkRestraintProps = typeof DefaultCrossLinkRestraintProps export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintProps> { return ComplexMeshVisual<CrossLinkRestraintProps>({ defaultProps: DefaultCrossLinkRestraintProps, - createMesh: createCrossLinkRestraintCylinderMesh, + createGeometry: createCrossLinkRestraintCylinderMesh, createLocationIterator: CrossLinkRestraintIterator, getLoci: getLinkLoci, mark: markLink, diff --git a/src/mol-geo/representation/structure/visual/gaussian-density-volume.ts b/src/mol-geo/representation/structure/visual/gaussian-density-volume.ts index 932cdad4e138e4a19f03360ecd814e456179ee92..94bcd294a930ba8498e324a9eea043890225a34b 100644 --- a/src/mol-geo/representation/structure/visual/gaussian-density-volume.ts +++ b/src/mol-geo/representation/structure/visual/gaussian-density-volume.ts @@ -11,9 +11,9 @@ import { UnitsDirectVolumeVisual, UnitsDirectVolumeParams } from '../units-visua import { StructureElementIterator, getElementLoci, markElement } from './util/element'; import { GaussianDensityProps, GaussianDensityParams, computeUnitGaussianDensityTexture } from 'mol-model/structure/structure/unit/gaussian-density'; import { paramDefaultValues } from 'mol-view/parameter'; -import { DirectVolume2d, DirectVolume3d } from '../../../geometry/direct-volume/direct-volume'; +import { DirectVolume } from '../../../geometry/direct-volume/direct-volume'; -async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume2d | DirectVolume3d): Promise<DirectVolume2d | DirectVolume3d> { +async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> { const { webgl } = props if (webgl === undefined) throw new Error('createGaussianDensityVolume requires `webgl` in props') @@ -22,11 +22,7 @@ async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, stru const densityTextureData = await computeUnitGaussianDensityTexture(unit, p, oldTexture).runInContext(ctx) const { transform, texture, bbox, gridDimension } = densityTextureData - directVolume = texture.depth === 0 ? - DirectVolume2d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume2d) : - DirectVolume3d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume3d) - - return directVolume; + return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume) } export const GaussianDensityVolumeParams = { diff --git a/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts b/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts index 8036e70041e7c44d851850bfe2bf6c88e2f515b2..043f8dc93995af335aa768ec90f8adec6470ebc4 100644 --- a/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts +++ b/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, Structure, StructureElement, ElementIndex } from 'mol-model/structure'; +import { Unit, Structure } from 'mol-model/structure'; import { UnitsVisual, VisualUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../geometry/mesh/mesh'; @@ -13,11 +13,9 @@ import { StructureElementIterator, getElementLoci, markElement } from './util/el import { computeMarchingCubesMesh } from '../../../util/marching-cubes/algorithm'; import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure/structure/unit/gaussian-density'; import { paramDefaultValues } from 'mol-view/parameter'; -import { SizeTheme } from 'mol-view/theme/size'; -import { OrderedSet } from 'mol-data/int'; async function createGaussianSurfaceMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> { - const { smoothness, radiusOffset } = props + const { smoothness } = props const { transform, field, idField } = await unit.computeGaussianDensity(props, ctx) const params = { @@ -28,46 +26,6 @@ async function createGaussianSurfaceMesh(ctx: RuntimeContext, unit: Unit, struct const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx) Mesh.transformImmediate(surface, transform) - - if (props.useGpu) { - console.time('find max element radius') - const { elements } = unit - const n = OrderedSet.size(elements) - const l = StructureElement.create(unit) - const sizeTheme = SizeTheme({ name: 'physical' }) - const radius = (index: number) => { - l.element = index as ElementIndex - return sizeTheme.size(l) - } - let maxRadius = 0 - for (let i = 0; i < n; ++i) { - const r = radius(OrderedSet.getAt(elements, i)) + radiusOffset - if (maxRadius < r) maxRadius = r - } - console.timeEnd('find max element radius') - - console.time('find closest element for vertices') - const { lookup3d } = unit - - const { vertexCount, vertexBuffer, groupBuffer } = surface - const vertices = vertexBuffer.ref.value - const groups = groupBuffer.ref.value - for (let i = 0; i < vertexCount; ++i) { - const r = lookup3d.find(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2], maxRadius * 2) - let minDsq = Infinity - let group = 0 - for (let j = 0, jl = r.count; j < jl; ++j) { - const dSq = r.squaredDistances[j] - if (dSq < minDsq) { - minDsq = dSq - group = r.indices[j] - } - } - groups[i] = group - } - console.timeEnd('find closest element for vertices') - } - Mesh.computeNormalsImmediate(surface) Mesh.uniformTriangleGroup(surface) diff --git a/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts b/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts index d12124bfa48eadc937354b0100aa129d893b7472..5503cae068a2afa4d51fa9ac0ff3e1c746234494 100644 --- a/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts @@ -62,7 +62,7 @@ export type InterUnitLinkProps = typeof DefaultInterUnitLinkProps export function InterUnitLinkVisual(): ComplexVisual<InterUnitLinkProps> { return ComplexMeshVisual<InterUnitLinkProps>({ defaultProps: DefaultInterUnitLinkProps, - createMesh: createInterUnitLinkCylinderMesh, + createGeometry: createInterUnitLinkCylinderMesh, createLocationIterator: LinkIterator.fromStructure, getLoci: getLinkLoci, mark: markLink, diff --git a/src/mol-geo/representation/structure/visual/util/common.ts b/src/mol-geo/representation/structure/visual/util/common.ts index ef59fe0f370c855cd150a25d3b9c35592f00c4ba..cadf7fef3acfa3d5e50d5ae02b2600809db40edf 100644 --- a/src/mol-geo/representation/structure/visual/util/common.ts +++ b/src/mol-geo/representation/structure/visual/util/common.ts @@ -8,14 +8,16 @@ import { Unit, Structure } from 'mol-model/structure'; import { LocationIterator } from '../../../../util/location-iterator'; import { Mesh } from '../../../../geometry/mesh/mesh'; import { StructureProps } from '../..'; -import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject, createDirectVolume2dRenderObject, createDirectVolume3dRenderObject } from 'mol-gl/render-object'; +import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject, createDirectVolumeRenderObject } from 'mol-gl/render-object'; import { RuntimeContext } from 'mol-task'; import { TransformData, createIdentityTransform, createTransform } from '../../../../geometry/transform-data'; import { Points } from '../../../../geometry/points/points'; import { createRenderableState } from '../../../../geometry/geometry'; import { Mat4 } from 'mol-math/linear-algebra'; import { Lines } from '../../../../geometry/lines/lines'; -import { DirectVolume2d, DirectVolume3d } from '../../../../geometry/direct-volume/direct-volume'; +import { DirectVolume } from '../../../../geometry/direct-volume/direct-volume'; +import { SizeProps } from 'mol-geo/geometry/size-data'; +import { ColorProps } from 'mol-geo/geometry/color-data'; export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) { const unitCount = units.length @@ -27,6 +29,39 @@ export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformDat return createTransform(array, unitCount, transformData) } +export const UnitKindInfo = { + 'atomic': {}, + 'spheres': {}, + 'gaussians': {}, +} +export type UnitKind = keyof typeof UnitKindInfo +export const UnitKindNames = Object.keys(UnitKindInfo) +export const UnitKindOptions = UnitKindNames.map(n => [n, n] as [UnitKind, string]) + +export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) { + for (let i = 0, il = unitKinds.length; i < il; ++i) { + if (Unit.isAtomic(unit) && unitKinds[i] === 'atomic') return true + if (Unit.isSpheres(unit) && unitKinds[i] === 'spheres') return true + if (Unit.isGaussians(unit) && unitKinds[i] === 'gaussians') return true + } + return false +} + +export function sizeChanged(oldProps: SizeProps, newProps: SizeProps) { + return ( + oldProps.sizeTheme !== newProps.sizeTheme || + oldProps.sizeValue !== newProps.sizeValue || + oldProps.sizeFactor !== newProps.sizeFactor + ) +} + +export function colorChanged(oldProps: ColorProps, newProps: ColorProps) { + return ( + oldProps.colorTheme !== newProps.colorTheme || + oldProps.colorValue !== newProps.colorValue + ) +} + // mesh type StructureMeshProps = Mesh.Props & StructureProps @@ -63,20 +98,17 @@ type StructureLinesProps = Lines.Props & StructureProps export async function createUnitsLinesRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, lines: Lines, locationIt: LocationIterator, props: StructureLinesProps) { const transform = createUnitsTransform(group) const values = await Lines.createValues(ctx, lines, transform, locationIt, props) - console.log('values', values) const state = createRenderableState(props) return createLinesRenderObject(values, state) } // direct-volume -type StructureDirectVolumeProps = DirectVolume2d.Props & DirectVolume3d.Props & StructureProps +type StructureDirectVolumeProps = DirectVolume.Props & StructureProps -export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume2d | DirectVolume3d, locationIt: LocationIterator, props: StructureDirectVolumeProps) { - // TODO transform support - // const transform = createUnitsTransform(group) +export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume, locationIt: LocationIterator, props: StructureDirectVolumeProps) { + const transform = createUnitsTransform(group) + const values = await DirectVolume.createValues(ctx, directVolume, transform, locationIt, props) const state = createRenderableState(props) - return directVolume.kind === 'direct-volume-2d' ? - createDirectVolume2dRenderObject(await DirectVolume2d.createValues(ctx, directVolume, props), state) : - createDirectVolume3dRenderObject(await DirectVolume3d.createValues(ctx, directVolume, props), state) + return createDirectVolumeRenderObject(values, state) } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/direct-volume.ts b/src/mol-geo/representation/volume/direct-volume.ts index 8e5eaecdf20e2df0264bdd0430037c73a5d835b9..1dd1e9446bcad8bab3e1f2918d6fb1acd4917277 100644 --- a/src/mol-geo/representation/volume/direct-volume.ts +++ b/src/mol-geo/representation/volume/direct-volume.ts @@ -7,18 +7,20 @@ import { VolumeData } from 'mol-model/volume' import { RuntimeContext } from 'mol-task' import { VolumeVisual, VolumeRepresentation } from '.'; -import { DirectVolume2dRenderObject, createDirectVolume2dRenderObject, DirectVolume3dRenderObject, createDirectVolume3dRenderObject } from 'mol-gl/render-object'; +import { DirectVolumeRenderObject, createDirectVolumeRenderObject } from 'mol-gl/render-object'; import { PickingId } from '../../geometry/picking'; import { MarkerAction } from '../../geometry/marker-data'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { createRenderableState, updateRenderableState, Geometry } from '../../geometry/geometry'; import { paramDefaultValues } from 'mol-view/parameter'; -import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume'; +import { DirectVolume } from '../../geometry/direct-volume/direct-volume'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { Box3D } from 'mol-math/geometry'; import { Context } from 'mol-gl/webgl/context'; -import { DirectVolume3dValues, DirectVolume2dValues } from 'mol-gl/renderable/direct-volume'; import { createTexture } from 'mol-gl/webgl/texture'; +import { LocationIterator } from 'mol-geo/util/location-iterator'; +import { NullLocation } from 'mol-model/location'; +import { createIdentityTransform } from 'mol-geo/geometry/transform-data'; function getBoundingBox(gridDimension: Vec3, transform: Mat4) { const bbox = Box3D.empty() @@ -83,7 +85,7 @@ function createVolumeTexture2d(volume: VolumeData, maxTextureSize: number) { return textureImage } -export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume2d) { +export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume) { const gridDimension = volume.data.space.dimensions as Vec3 const textureImage = createVolumeTexture2d(volume, webgl.maxTextureSize) // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3) @@ -96,7 +98,7 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear') texture.load(textureImage) - return DirectVolume2d.create(bbox, dim, transform, texture, directVolume) + return DirectVolume.create(bbox, dim, transform, texture, directVolume) } // 3d volume texture @@ -123,7 +125,7 @@ function createVolumeTexture3d(volume: VolumeData) { return textureVolume } -export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume3d) { +export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume) { const gridDimension = volume.data.space.dimensions as Vec3 const textureVolume = createVolumeTexture3d(volume) const transform = VolumeData.getGridToCartesianTransform(volume) @@ -132,23 +134,23 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear') texture.load(textureVolume) - return DirectVolume3d.create(bbox, gridDimension, transform, texture, directVolume) + return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume) } // export const DirectVolumeParams = { ...Geometry.Params, - ...DirectVolume2d.Params + ...DirectVolume.Params } export const DefaultDirectVolumeProps = paramDefaultValues(DirectVolumeParams) export type DirectVolumeProps = typeof DefaultDirectVolumeProps export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> { let currentProps = DefaultDirectVolumeProps - let renderObject: DirectVolume2dRenderObject | DirectVolume3dRenderObject + let renderObject: DirectVolumeRenderObject let currentVolume: VolumeData - let directVolume: DirectVolume2d | DirectVolume3d + let directVolume: DirectVolume async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<DirectVolumeProps> = {}) { const { webgl } = props @@ -160,16 +162,17 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> { } const state = createRenderableState(currentProps) + const locationIt = LocationIterator(1, 1, () => NullLocation) + const transform = createIdentityTransform() if (webgl.isWebGL2) { - console.log('creating 3d volume') - directVolume = await createDirectVolume3d(ctx, webgl, volume, directVolume as DirectVolume3d) - const values = await DirectVolume3d.createValues(ctx, directVolume as DirectVolume3d, currentProps) - renderObject = createDirectVolume3dRenderObject(values, state) + directVolume = await createDirectVolume3d(ctx, webgl, volume, directVolume) + const values = await DirectVolume.createValues(ctx, directVolume, transform, locationIt, currentProps) + renderObject = createDirectVolumeRenderObject(values, state) } else { - directVolume = await createDirectVolume2d(ctx, webgl, volume, directVolume as DirectVolume2d) - const values = await DirectVolume2d.createValues(ctx, directVolume as DirectVolume2d, currentProps) - renderObject = createDirectVolume2dRenderObject(values, state) + directVolume = await createDirectVolume2d(ctx, webgl, volume, directVolume) + const values = await DirectVolume.createValues(ctx, directVolume, transform, locationIt, currentProps) + renderObject = createDirectVolumeRenderObject(values, state) } } @@ -182,11 +185,7 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> { // newProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative) } - if (webgl.isWebGL2) { - DirectVolume3d.updateValues(renderObject.values as DirectVolume3dValues, newProps) - } else { - DirectVolume2d.updateValues(renderObject.values as DirectVolume2dValues, newProps) - } + DirectVolume.updateValues(renderObject.values, newProps) updateRenderableState(renderObject.state, newProps) currentProps = newProps diff --git a/src/mol-gl/render-object.ts b/src/mol-gl/render-object.ts index f600d65a55be3f7acba20bfe1782bb821abc3df3..34dff9145a52c963fec6d56525bb790c7138c361 100644 --- a/src/mol-gl/render-object.ts +++ b/src/mol-gl/render-object.ts @@ -9,7 +9,7 @@ import { RenderableValues } from './renderable/schema'; import { idFactory } from 'mol-util/id-factory'; import { Context } from './webgl/context'; import { GaussianDensityValues, GaussianDensityRenderable } from './renderable/gaussian-density'; -import { DirectVolume2dValues, DirectVolume2dRenderable, DirectVolume3dValues, DirectVolume3dRenderable } from './renderable/direct-volume'; +import { DirectVolumeValues, DirectVolumeRenderable } from './renderable/direct-volume'; const getNextId = idFactory(0, 0x7FFFFFFF) @@ -18,10 +18,9 @@ export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', value export interface PointsRenderObject extends BaseRenderObject { type: 'points', values: PointsValues } export interface LinesRenderObject extends BaseRenderObject { type: 'lines', values: LinesValues } export interface GaussianDensityRenderObject extends BaseRenderObject { type: 'gaussian-density', values: GaussianDensityValues } -export interface DirectVolume2dRenderObject extends BaseRenderObject { type: 'direct-volume-2d', values: DirectVolume2dValues } -export interface DirectVolume3dRenderObject extends BaseRenderObject { type: 'direct-volume-3d', values: DirectVolume3dValues } +export interface DirectVolumeRenderObject extends BaseRenderObject { type: 'direct-volume', values: DirectVolumeValues } -export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject | DirectVolume2dRenderObject | DirectVolume3dRenderObject +export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject | DirectVolumeRenderObject // @@ -37,11 +36,8 @@ export function createLinesRenderObject(values: LinesValues, state: RenderableSt export function createGaussianDensityRenderObject(values: GaussianDensityValues, state: RenderableState): GaussianDensityRenderObject { return { id: getNextId(), type: 'gaussian-density', values, state } } -export function createDirectVolume2dRenderObject(values: DirectVolume2dValues, state: RenderableState): DirectVolume2dRenderObject { - return { id: getNextId(), type: 'direct-volume-2d', values, state } -} -export function createDirectVolume3dRenderObject(values: DirectVolume3dValues, state: RenderableState): DirectVolume3dRenderObject { - return { id: getNextId(), type: 'direct-volume-3d', values, state } +export function createDirectVolumeRenderObject(values: DirectVolumeValues, state: RenderableState): DirectVolumeRenderObject { + return { id: getNextId(), type: 'direct-volume', values, state } } export function createRenderable(ctx: Context, o: RenderObject): Renderable<any> { @@ -50,7 +46,6 @@ export function createRenderable(ctx: Context, o: RenderObject): Renderable<any> case 'points': return PointsRenderable(ctx, o.id, o.values, o.state) case 'lines': return LinesRenderable(ctx, o.id, o.values, o.state) case 'gaussian-density': return GaussianDensityRenderable(ctx, o.id, o.values, o.state) - case 'direct-volume-2d': return DirectVolume2dRenderable(ctx, o.id, o.values, o.state) - case 'direct-volume-3d': return DirectVolume3dRenderable(ctx, o.id, o.values, o.state) + case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values, o.state) } } \ No newline at end of file diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 356ad888e9bb07ecd801d3c5a066805b24daf08a..0f9ddcbbe2f9c3a47952449055f2d40b8d73e2e5 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -11,7 +11,22 @@ import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema import { DirectVolumeShaderCode } from '../shader-code'; import { ValueCell } from 'mol-util'; -export const DirectVolumeBaseSchema = { +export const DirectVolumeSchema = { + aColor: AttributeSpec('float32', 3, 0), // TODO not used, just for type checking + uColor: UniformSpec('v3'), + uColorTexDim: UniformSpec('v2'), + tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'), + dColorType: DefineSpec('string', ['uniform', 'instance', 'group', 'group_instance']), + + uMarkerTexDim: UniformSpec('v2'), + tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), + + uInstanceCount: UniformSpec('i'), + uGroupCount: UniformSpec('i'), + + aInstance: AttributeSpec('float32', 1, 1), + aTransform: AttributeSpec('float32', 16, 1), + drawCount: ValueSpec('number'), instanceCount: ValueSpec('number'), @@ -30,54 +45,24 @@ export const DirectVolumeBaseSchema = { uGridDim: UniformSpec('v3'), dRenderMode: DefineSpec('string', ['isosurface', 'volume']), tTransferTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'), + + dGridTexType: DefineSpec('string', ['2d', '3d']), + uGridTexDim: UniformSpec('v3'), + tGridTex: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), } -export type DirectVolumeBaseSchema = typeof DirectVolumeBaseSchema -export type DirectVolumeBaseValues = Values<DirectVolumeBaseSchema> +export type DirectVolumeSchema = typeof DirectVolumeSchema +export type DirectVolumeValues = Values<DirectVolumeSchema> -function getInternalValues(ctx: Context, id: number): InternalValues { - return { +export function DirectVolumeRenderable(ctx: Context, id: number, values: DirectVolumeValues, state: RenderableState): Renderable<DirectVolumeValues> { + const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema } + const internalValues: InternalValues = { uObjectId: ValueCell.create(id) } -} - -function DirectVolumeRenderable<T extends DirectVolumeBaseValues, S extends DirectVolumeBaseSchema>(ctx: Context, id: number, values: T, state: RenderableState, schema: S): Renderable<T> { - const fullSchema = Object.assign({}, GlobalUniformSchema, InternalSchema, schema) - const internalValues = getInternalValues(ctx, id) - const fullValues = Object.assign({}, values, internalValues) const shaderCode = DirectVolumeShaderCode - const renderItem = createRenderItem(ctx, 'triangles', shaderCode, fullSchema, fullValues) + const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }) const renderable = createRenderable(renderItem, values, state); Object.defineProperty(renderable, 'opaque', { get: () => false }); return renderable -} - -// via 2d texture - -export const DirectVolume2dSchema = { - ...DirectVolumeBaseSchema, - dGridTexType: DefineSpec('string', ['2d']), - uGridTexDim: UniformSpec('v2'), - tGridTex: TextureSpec('texture2d', 'rgba', 'ubyte', 'linear'), -} -export type DirectVolume2dSchema = typeof DirectVolume2dSchema -export type DirectVolume2dValues = Values<DirectVolume2dSchema> - -export function DirectVolume2dRenderable(ctx: Context, id: number, values: DirectVolume2dValues, state: RenderableState): Renderable<DirectVolume2dValues> { - return DirectVolumeRenderable(ctx, id, values, state, DirectVolume2dSchema) -} - -// via 3d texture - -export const DirectVolume3dSchema = { - ...DirectVolumeBaseSchema, - dGridTexType: DefineSpec('string', ['3d']), - tGridTex: TextureSpec('texture3d', 'rgba', 'ubyte', 'linear'), -} -export type DirectVolume3dSchema = typeof DirectVolume3dSchema -export type DirectVolume3dValues = Values<DirectVolume3dSchema> - -export function DirectVolume3dRenderable(ctx: Context, id: number, values: DirectVolume3dValues, state: RenderableState): Renderable<DirectVolume3dValues> { - return DirectVolumeRenderable(ctx, id, values, state, DirectVolume3dSchema) } \ No newline at end of file diff --git a/src/mol-gl/renderable/gaussian-density.ts b/src/mol-gl/renderable/gaussian-density.ts index 1fe68a5aae45fe6a59550402226b1f9f31798425..e07143628e3072a56b856b868be844bae86f65d7 100644 --- a/src/mol-gl/renderable/gaussian-density.ts +++ b/src/mol-gl/renderable/gaussian-density.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable' import { Context } from '../webgl/context'; import { createRenderItem } from '../webgl/render-item'; -import { AttributeSpec, Values, UniformSpec, ValueSpec, DefineSpec } from './schema'; +import { AttributeSpec, Values, UniformSpec, ValueSpec, DefineSpec, TextureSpec } from './schema'; import { GaussianDensityShaderCode } from '../shader-code'; export const GaussianDensitySchema = { @@ -16,6 +16,7 @@ export const GaussianDensitySchema = { aRadius: AttributeSpec('float32', 1, 0), aPosition: AttributeSpec('float32', 3, 0), + aGroup: AttributeSpec('float32', 1, 0), uCurrentSlice: UniformSpec('f'), uCurrentX: UniformSpec('f'), @@ -24,9 +25,12 @@ export const GaussianDensitySchema = { uBboxMax: UniformSpec('v3'), uBboxSize: UniformSpec('v3'), uGridDim: UniformSpec('v3'), + uGridTexDim: UniformSpec('v3'), uAlpha: UniformSpec('f'), + tMinDistanceTex: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), - dDrawBuffers: DefineSpec('number'), + dGridTexType: DefineSpec('string', ['2d', '3d']), + dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']), } export type GaussianDensitySchema = typeof GaussianDensitySchema export type GaussianDensityValues = Values<GaussianDensitySchema> diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 1c25d2ab4258f5b6a94d944d29056a39654047aa..0e5826819aece71768921e486e6402fdba701bf2 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -43,6 +43,7 @@ export type KindValue = { 'image-float32': TextureImage<Float32Array> 'volume-uint8': TextureVolume<Uint8Array> 'volume-float32': TextureVolume<Float32Array> + 'texture': Texture 'texture2d': Texture 'texture3d': Texture @@ -59,7 +60,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues) const defineValues: DefineValues = {} const textureValues: TextureValues = {} const uniformValues: UniformValues = {} - Object.keys(values).forEach(k => { + Object.keys(schema).forEach(k => { if (schema[k].type === 'attribute') attributeValues[k] = values[k] if (schema[k].type === 'define') defineValues[k] = values[k] if (schema[k].type === 'texture') textureValues[k] = values[k] diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 26d8011827fadb8641fa83f2e1a7badeeaa47f6f..097d5bbd458e6db563e0872050c85e66ff49636c 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -14,39 +14,50 @@ export type DefineValues = { [k: string]: ValueCell<DefineType> } const shaderCodeId = idFactory() +export interface ShaderExtensions { + readonly standardDerivatives: boolean + readonly fragDepth: boolean +} + export interface ShaderCode { - id: number - vert: string - frag: string + readonly id: number + readonly vert: string + readonly frag: string + readonly extensions: ShaderExtensions } -export function ShaderCode(vert: string, frag: string): ShaderCode { - return { id: shaderCodeId(), vert, frag } +export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensions): ShaderCode { + return { id: shaderCodeId(), vert, frag, extensions } } export const PointsShaderCode = ShaderCode( require('mol-gl/shader/points.vert'), - require('mol-gl/shader/points.frag') + require('mol-gl/shader/points.frag'), + { standardDerivatives: false, fragDepth: false } ) export const LinesShaderCode = ShaderCode( require('mol-gl/shader/lines.vert'), - require('mol-gl/shader/lines.frag') + require('mol-gl/shader/lines.frag'), + { standardDerivatives: false, fragDepth: false } ) export const MeshShaderCode = ShaderCode( require('mol-gl/shader/mesh.vert'), - require('mol-gl/shader/mesh.frag') + require('mol-gl/shader/mesh.frag'), + { standardDerivatives: true, fragDepth: false } ) export const GaussianDensityShaderCode = ShaderCode( require('mol-gl/shader/gaussian-density.vert'), - require('mol-gl/shader/gaussian-density.frag') + require('mol-gl/shader/gaussian-density.frag'), + { standardDerivatives: false, fragDepth: false } ) export const DirectVolumeShaderCode = ShaderCode( require('mol-gl/shader/direct-volume.vert'), - require('mol-gl/shader/direct-volume.frag') + require('mol-gl/shader/direct-volume.frag'), + { standardDerivatives: false, fragDepth: true } ) export type ShaderDefines = { @@ -74,8 +85,20 @@ function getDefinesCode (defines: ShaderDefines) { return lines.join('\n') + '\n' } -const glsl100FragPrefix = `#extension GL_OES_standard_derivatives : enable -` +function getGlsl100FragPrefix(ctx: Context, extensions: ShaderExtensions) { + const prefix: string[] = [] + if (extensions.standardDerivatives) { + prefix.push('#extension GL_OES_standard_derivatives : enable') + prefix.push('#define enabledStandardDerivatives') + } + if (extensions.fragDepth) { + if (ctx.extensions.fragDepth) { + prefix.push('#extension GL_EXT_frag_depth : enable') + prefix.push('#define enabledFragDepth') + } + } + return prefix.join('\n') + '\n' +} const glsl300VertPrefix = `#version 300 es #define attribute in @@ -89,16 +112,20 @@ layout(location = 0) out highp vec4 out_FragColor; #define gl_FragColor out_FragColor #define gl_FragDepthEXT gl_FragDepth #define texture2D texture + +#define enabledStandardDerivatives +#define enabledFragDepth ` export function addShaderDefines(ctx: Context, defines: ShaderDefines, shaders: ShaderCode): ShaderCode { const { isWebGL2 } = ctx const header = getDefinesCode(defines) const vertPrefix = isWebGL2 ? glsl300VertPrefix : '' - const fragPrefix = isWebGL2 ? glsl300FragPrefix : glsl100FragPrefix + const fragPrefix = isWebGL2 ? glsl300FragPrefix : getGlsl100FragPrefix(ctx, shaders.extensions) return { id: shaderCodeId(), vert: `${vertPrefix}${header}${shaders.vert}`, - frag: `${fragPrefix}${header}${shaders.frag}` + frag: `${fragPrefix}${header}${shaders.frag}`, + extensions: shaders.extensions } } \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/apply-marker-color.glsl b/src/mol-gl/shader/chunks/apply-marker-color.glsl index 0c73020b06f145938655deeb00b56b625792f1be..14741ac1666722e7f79143945a24049ffa1db11b 100644 --- a/src/mol-gl/shader/chunks/apply-marker-color.glsl +++ b/src/mol-gl/shader/chunks/apply-marker-color.glsl @@ -1,6 +1,6 @@ -float marker = vMarker * 256.0; +float marker = vMarker * 255.0; if (marker > 0.1) { - if (mod(marker, 2.0) < 0.1) { + if (mod(marker, 2.0) > 0.1) { gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3); } else { gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3); diff --git a/src/mol-gl/shader/chunks/assign-color-varying.glsl b/src/mol-gl/shader/chunks/assign-color-varying.glsl index b946c1025af3ac570541316a7989167747ea8a1f..ad039eff1f54327427a3b9267b8b092ac095ba9a 100644 --- a/src/mol-gl/shader/chunks/assign-color-varying.glsl +++ b/src/mol-gl/shader/chunks/assign-color-varying.glsl @@ -7,9 +7,9 @@ #elif defined(dColorType_groupInstance) vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + aGroup, uColorTexDim).rgb; #elif defined(dColorType_objectPicking) - vColor = encodeIdRGBA(float(uObjectId)); + vColor = vec4(encodeIdRGB(float(uObjectId)), 1.0); #elif defined(dColorType_instancePicking) - vColor = encodeIdRGBA(aInstance); + vColor = vec4(encodeIdRGB(aInstance), 1.0); #elif defined(dColorType_groupPicking) - vColor = encodeIdRGBA(aGroup); + vColor = vec4(encodeIdRGB(aGroup), 1.0); #endif \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl b/src/mol-gl/shader/chunks/color-vert-params.glsl index 63318ea3c6c4caac90718bae39ae37267a77b860..f34e13df4cd569f7de08fcb7daff7350bf97d8ee 100644 --- a/src/mol-gl/shader/chunks/color-vert-params.glsl +++ b/src/mol-gl/shader/chunks/color-vert-params.glsl @@ -9,5 +9,5 @@ uniform sampler2D tColor; #elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) varying vec4 vColor; - #pragma glslify: encodeIdRGBA = require(../utils/encode-id-rgba.glsl) + #pragma glslify: encodeIdRGB = require(../utils/encode-id-rgb.glsl) #endif \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag index 82e6c5e40257235f6c990e09bd1e2eeeb3efe6c7..be043efe5976a30d8fda7721da0bba2dd4e38632 100644 --- a/src/mol-gl/shader/direct-volume.frag +++ b/src/mol-gl/shader/direct-volume.frag @@ -5,15 +5,11 @@ * @author Michael Krone <michael.krone@uni-tuebingen.de> */ -#if defined(dGridTexType_2d) - precision mediump sampler2D; -#elif defined(dGridTexType_3d) - precision mediump sampler3D; -#endif precision highp float; varying vec3 unitCoord; varying vec3 origPos; +varying float instance; uniform float uAlpha; uniform mat4 uInvView; @@ -21,42 +17,66 @@ uniform float uIsoValue; uniform vec3 uGridDim; uniform sampler2D tTransferTex; +uniform int uObjectId; +uniform int uInstanceCount; +uniform int uGroupCount; + +uniform vec3 uHighlightColor; +uniform vec3 uSelectColor; +uniform vec2 uMarkerTexDim; +uniform sampler2D tMarker; + #if defined(dGridTexType_2d) + precision mediump sampler2D; uniform sampler2D tGridTex; - uniform vec2 uGridTexDim; + uniform vec3 uGridTexDim; #elif defined(dGridTexType_3d) + precision mediump sampler3D; uniform sampler3D tGridTex; #endif -#if defined(dGridTexType_2d) - // TODO workaround due to some kind of GPU bug - float myMod(float a, float b) { - return a - b * float(int(a) / int(b)); - } - float myDiv(float a, float b) { - return float(int(a) / int(b)); - } +#if defined(dColorType_uniform) + uniform vec3 uColor; +#elif defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance) + uniform vec2 uColorTexDim; + uniform sampler2D tColor; +#endif + +#pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl) +#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl) +#pragma glslify: decodeIdRGB = require(./utils/decode-id-rgb.glsl) +#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl) +#pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl) + +// uniform vec3 uLightPosition; +uniform vec3 uLightColor; +uniform vec3 uLightAmbient; +uniform mat4 uView; +#pragma glslify: attenuation = require(./utils/attenuation.glsl) +#pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl) +#pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl) + +const float specularScale = 0.15; +const float shininess = 200.0; +const float roughness = 100.0; +const float albedo = 0.95; + +#if defined(dGridTexType_2d) vec4 textureVal(vec3 pos) { - float zSlice0 = floor(pos.z * uGridDim.z); - float column0 = myMod(zSlice0 * uGridDim.x, uGridTexDim.x) / uGridDim.x; - float row0 = floor(myDiv(zSlice0 * uGridDim.x, uGridTexDim.x)); - vec2 coord0 = (vec2(column0 * uGridDim.x, row0 * uGridDim.y) + (pos.xy * uGridDim.xy)) / uGridTexDim; - vec4 color0 = texture2D(tGridTex, coord0); - - float zSlice1 = zSlice0 + 1.0; - float column1 = myMod(zSlice1 * uGridDim.x, uGridTexDim.x) / uGridDim.x; - float row1 = floor(myDiv(zSlice1 * uGridDim.x, uGridTexDim.x)); - vec2 coord1 = (vec2(column1 * uGridDim.x, row1 * uGridDim.y) + (pos.xy * uGridDim.xy)) / uGridTexDim; - vec4 color1 = texture2D(tGridTex, coord1); - - float delta0 = abs((pos.z * uGridDim.z) - zSlice0); - return mix(color0, color1, delta0); + return texture3dFrom2dLinear(tGridTex, pos, uGridDim, uGridTexDim.xy); + } + vec4 textureGroup(vec3 pos) { + vec3 nearestPos = floor(pos * uGridDim + 0.5) / uGridDim + 0.5 / uGridDim; + return texture3dFrom2dNearest(tGridTex, nearestPos, uGridDim, uGridTexDim.xy); } #elif defined(dGridTexType_3d) vec4 textureVal(vec3 pos) { return texture(tGridTex, pos); } + vec4 textureGroup(vec3 pos) { + return texelFetch(tGridTex, ivec3(pos * uGridDim), 0); + } #endif vec4 transferFunction(float value) { @@ -64,7 +84,6 @@ vec4 transferFunction(float value) { } const float gradOffset = 0.5; -const vec3 color = vec3(0.45, 0.55, 0.8); vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) { vec3 scaleVol = vec3(1.0) / uGridDim; @@ -78,6 +97,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) { vec3 isoPos; float tmp; + vec3 color = vec3(0.45, 0.55, 0.8); vec3 gradient = vec3(1.0); vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0); vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0); @@ -103,28 +123,67 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) { tmp = ((prevValue - uIsoValue) / ((prevValue - uIsoValue) - (value - uIsoValue))); isoPos = mix(pos - step, pos, tmp); - // compute gradient by central differences - gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a; - gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a; - gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a; - gradient = normalize(gradient); - - float d = float(dot(gradient, viewDir) > 0.0); - gradient = (2.0 * d - 1.0) * gradient; - - src.rgb = color.rgb * abs(dot(gradient, viewDir)); - src.a = uAlpha; - - // draw interior darker - if( (prevValue - uIsoValue) > 0.0 ) { - src.rgb *= 0.5; - } - - src.rgb *= src.a; - dst = (1.0 - dst.a) * src + dst; // standard blending - if(dst.a >= 1.0) { - break; - } + #if defined(dColorType_objectPicking) + return vec4(encodeIdRGB(float(uObjectId)), 1.0); + #elif defined(dColorType_instancePicking) + return vec4(encodeIdRGB(instance), 1.0); + #elif defined(dColorType_groupPicking) + float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5); + return vec4(encodeIdRGB(group), 1.0); + #else + // compute gradient by central differences + gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a; + gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a; + gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a; + gradient = normalize(gradient); + float d = float(dot(gradient, viewDir) > 0.0); + gradient = (2.0 * d - 1.0) * gradient; + + float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5); + + #if defined(dColorType_instance) + color = readFromTexture(tColor, instance, uColorTexDim).rgb; + #elif defined(dColorType_group) + color = readFromTexture(tColor, group, uColorTexDim).rgb; + #elif defined(dColorType_groupInstance) + color = readFromTexture(tColor, instance * float(uGroupCount) + group, uColorTexDim).rgb; + #endif + + vec3 L = normalize(viewDir); // light direction + vec3 V = normalize(viewDir); // eye direction + vec3 N = normalize(gradient); // surface normal + + // compute our diffuse & specular terms + float specular = calculateSpecular(L, V, N, shininess) * specularScale; + vec3 diffuse = uLightColor * calculateDiffuse(L, V, N, roughness, albedo); + vec3 ambient = uLightAmbient; + + // add the lighting + vec3 finalColor = color.rgb * (diffuse + ambient) + specular; + + src.rgb = finalColor; + src.a = uAlpha; + + float marker = readFromTexture(tMarker, instance * float(uGroupCount) + group, uMarkerTexDim).a * 256.0; + if (marker > 0.1) { + if (mod(marker, 2.0) < 0.1) { + src.rgb = mix(uHighlightColor, src.rgb, 0.3); + } else { + src.rgb = mix(uSelectColor, src.rgb, 0.3); + } + } + + // draw interior darker + if( (prevValue - uIsoValue) > 0.0 ) { + src.rgb *= 0.5; + } + + src.rgb *= src.a; + dst = (1.0 - dst.a) * src + dst; // standard blending + if(dst.a >= 1.0) { + break; + } + #endif } prevValue = value; #endif @@ -144,6 +203,6 @@ void main () { gl_FragColor = raymarch(startLoc, step, normalize(cameraPos)); if (length(gl_FragColor.rgb) < 0.00001) discard; #if defined(dRenderMode_volume) - gl_FragColor.a = uAlpha; + gl_FragColor.a *= uAlpha; #endif } \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.vert b/src/mol-gl/shader/direct-volume.vert index 7ab9edae8c130aeadf0e3db00943600e66f4a3d9..248ddcaebd330ef6df7264e57b211948962ff901 100644 --- a/src/mol-gl/shader/direct-volume.vert +++ b/src/mol-gl/shader/direct-volume.vert @@ -8,9 +8,12 @@ precision highp float; attribute vec3 aPosition; +attribute mat4 aTransform; +attribute float aInstance; varying vec3 unitCoord; varying vec3 origPos; +varying float instance; uniform vec3 uBboxSize; uniform vec3 uBboxMin; @@ -23,5 +26,6 @@ void main() { unitCoord = aPosition + vec3(0.5); vec4 mvPosition = uModelView * vec4(unitCoord * uBboxSize + uBboxMin, 1.0); origPos = unitCoord * uBboxSize + uBboxMin; + instance = aInstance; gl_Position = uProjection * mvPosition; } \ No newline at end of file diff --git a/src/mol-gl/shader/gaussian-density.frag b/src/mol-gl/shader/gaussian-density.frag index 546267b47df69a4640019cd537ffad4ba96b4272..ce7bacf7acdf79d87af242c8d8e20379041f83dd 100644 --- a/src/mol-gl/shader/gaussian-density.frag +++ b/src/mol-gl/shader/gaussian-density.frag @@ -7,8 +7,22 @@ precision highp float; -varying vec3 position; -varying float radius; +varying vec3 vPosition; +varying float vRadius; +#if defined(dCalcType_groupId) + #if defined(dGridTexType_2d) + precision mediump sampler2D; + uniform sampler2D tMinDistanceTex; + uniform vec3 uGridTexDim; + #elif defined(dGridTexType_3d) + precision highp sampler3D; + uniform sampler3D tMinDistanceTex; + #endif + varying float vGroup; +#endif + +#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl) +#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl) uniform vec3 uBboxSize; uniform vec3 uBboxMin; @@ -19,40 +33,41 @@ uniform float uCurrentX; uniform float uCurrentY; uniform float uAlpha; -#if dDrawBuffers >= 4 - layout(location = 1) out vec4 out1; - layout(location = 2) out vec4 out2; - layout(location = 3) out vec4 out3; -#endif -#if dDrawBuffers >= 8 - layout(location = 4) out vec4 out4; - layout(location = 5) out vec4 out5; - layout(location = 6) out vec4 out6; - layout(location = 7) out vec4 out7; +#if defined(dCalcType_groupId) + #if defined(dGridTexType_2d) + vec4 textureMinDist(vec3 pos) { + return texture3dFrom2dNearest(tMinDistanceTex, pos, uGridDim, uGridTexDim.xy); + } + #elif defined(dGridTexType_3d) + vec4 textureMinDist(vec3 pos) { + return texture(tMinDistanceTex, pos); + } + #endif #endif -float calcDensity(float x, float y, float z, float radiusSq) { - vec3 fragPos = vec3(x, y, z) / uGridDim; - float dist = distance(fragPos * uBboxSize, position * uBboxSize); - float density = exp(-uAlpha * ((dist * dist) / radiusSq)); - return density; -} - -const vec3 color = vec3(1.0, 1.0, 1.0); +// encode distance logarithmically with given maxDistance +const float maxDistance = 10000.0; +const float distLogFactor = log(maxDistance + 1.0); +float encodeDistLog(float dist) { return log(dist + 1.0) / distLogFactor; } +float decodeDistLog(float logDist) { return exp(logDist * distLogFactor) - 1.0; } void main() { - float radiusSq = radius * radius; vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5; - gl_FragColor = vec4(color, calcDensity(v.x, v.y, uCurrentSlice, radiusSq)); - #if dDrawBuffers >= 4 - out1 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 1.0, radiusSq)); - out2 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 2.0, radiusSq)); - out3 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 3.0, radiusSq)); - #endif - #if dDrawBuffers >= 8 - out4 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 4.0, radiusSq)); - out5 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 5.0, radiusSq)); - out6 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 6.0, radiusSq)); - out7 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 7.0, radiusSq)); + vec3 fragPos = vec3(v.x, v.y, uCurrentSlice) / uGridDim; + float dist = distance(fragPos * uBboxSize, vPosition * uBboxSize); + + #if defined(dCalcType_density) + float radiusSq = vRadius * vRadius; + float density = exp(-uAlpha * ((dist * dist) / radiusSq)); + gl_FragColor = vec4(density); + #elif defined(dCalcType_minDistance) + gl_FragColor.a = 1.0 - encodeDistLog(dist); + #elif defined(dCalcType_groupId) + float minDistance = decodeDistLog(1.0 - textureMinDist(fragPos).a); + // TODO verify `length(uBboxSize / uGridDim) * 2.0` + // on some machines `* 2.0` is needed while on others `* 0.5` works + if (dist > minDistance + length(uBboxSize / uGridDim) * 0.5) + discard; + gl_FragColor.rgb = encodeIdRGB(vGroup); #endif } \ No newline at end of file diff --git a/src/mol-gl/shader/gaussian-density.vert b/src/mol-gl/shader/gaussian-density.vert index 217f0085a8a5d87aa2f6341e87f49bf122f54861..f7557182942ce69b7861146dd3da32ffc3b5c52d 100644 --- a/src/mol-gl/shader/gaussian-density.vert +++ b/src/mol-gl/shader/gaussian-density.vert @@ -10,8 +10,13 @@ precision highp float; attribute vec3 aPosition; attribute float aRadius; -varying vec3 position; -varying float radius; +varying vec3 vPosition; +varying float vRadius; + +#if defined(dCalcType_groupId) + attribute float aGroup; + varying float vGroup; +#endif uniform vec3 uBboxSize; uniform vec3 uBboxMin; @@ -20,9 +25,12 @@ uniform vec3 uGridDim; uniform float uCurrentSlice; void main() { - radius = aRadius; + vRadius = aRadius; + #if defined(dCalcType_groupId) + vGroup = aGroup; + #endif float scale = max(uBboxSize.z, max(uBboxSize.x, uBboxSize.y)); - gl_PointSize = (radius / scale) * max(uGridDim.x, uGridDim.y) * 6.0; - position = (aPosition - uBboxMin) / uBboxSize; - gl_Position = vec4(position * 2.0 - 1.0, 1.0); + gl_PointSize = (vRadius / scale) * max(uGridDim.x, uGridDim.y) * 6.0; + vPosition = (aPosition - uBboxMin) / uBboxSize; + gl_Position = vec4(vPosition * 2.0 - 1.0, 1.0); } \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index ce0233e3b80d9e30bd2aebaac2708bc7c84408ef..2195060272cbc736d150e39d3d58166569d1f2b3 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/src/mol-gl/shader/mesh.frag @@ -15,7 +15,7 @@ uniform vec3 uLightColor; uniform vec3 uLightAmbient; uniform mat4 uView; -#ifndef dFlatShaded +#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives) varying vec3 vNormal; #endif @@ -23,9 +23,9 @@ uniform mat4 uView; #pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl) #pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl) -const float specularScale = 0.65; -const float shininess = 100.0; -const float roughness = 5.0; +const float specularScale = 0.15; +const float shininess = 200.0; +const float roughness = 100.0; const float albedo = 0.95; void main() { @@ -45,7 +45,7 @@ void main() { vec3 V = normalize(vViewPosition); // eye direction // surface normal - #ifdef dFlatShaded + #if defined(dFlatShaded) && defined(enabledStandardDerivatives) vec3 fdx = dFdx(vViewPosition); vec3 fdy = dFdy(vViewPosition); vec3 N = -normalize(cross(fdx, fdy)); diff --git a/src/mol-gl/shader/utils/decode-float-rgb.glsl b/src/mol-gl/shader/utils/decode-float-rgb.glsl new file mode 100644 index 0000000000000000000000000000000000000000..60a586e2865b11b21dd2857d5ddc3936975f2c3a --- /dev/null +++ b/src/mol-gl/shader/utils/decode-float-rgb.glsl @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +float decodeFloatRGB(const in vec3 rgb) { + return rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0; +} + +#pragma glslify: export(decodeFloatRGB) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/decode-float-rgba.glsl b/src/mol-gl/shader/utils/decode-float-rgba.glsl deleted file mode 100644 index c4b57eb4a2ad30c02bb5d511946cb15d605c2794..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/utils/decode-float-rgba.glsl +++ /dev/null @@ -1,5 +0,0 @@ -float decodeFloatRGBA(const in vec4 rgba) { - return dot(rgba, vec4(1.0, 1/255.0, 1/65025.0, 1/16581375.0)); -} - -#pragma glslify: export(decodeFloatRGBA) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/decode-id-rgb.glsl b/src/mol-gl/shader/utils/decode-id-rgb.glsl new file mode 100644 index 0000000000000000000000000000000000000000..1a4789e496505cf27c94d0086f1f928f6a58d1fd --- /dev/null +++ b/src/mol-gl/shader/utils/decode-id-rgb.glsl @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +#pragma glslify: decodeFloatRGB = require(../utils/decode-float-rgb.glsl) + +float decodeIdRGB(const in vec3 v) { + return decodeFloatRGB(v) - 1.0; +} + +#pragma glslify: export(decodeIdRGB) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/encode-float-rgb.glsl b/src/mol-gl/shader/utils/encode-float-rgb.glsl new file mode 100644 index 0000000000000000000000000000000000000000..1cecbf759e9efc95c259c9e2389c052b6a627c59 --- /dev/null +++ b/src/mol-gl/shader/utils/encode-float-rgb.glsl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +// TODO use myMod and myDiv to fix issues with picking? + +vec3 encodeFloatRGB(in float value) { + value = clamp(value, 0.0, 16777216.0); + vec3 c = vec3(0.0); + c.b = mod(value, 256.0); + value = floor(value / 256.0); + c.g = mod(value, 256.0); + value = floor(value / 256.0); + c.r = mod(value, 256.0); + return c / 255.0; +} + +#pragma glslify: export(encodeFloatRGB) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/encode-float-rgba.glsl b/src/mol-gl/shader/utils/encode-float-rgba.glsl deleted file mode 100644 index fe67ca0d81e099c2bc1e6119238d364e724cc161..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/utils/encode-float-rgba.glsl +++ /dev/null @@ -1,12 +0,0 @@ -vec4 encodeFloatRGBA(in float value) { - value = clamp(value, 0., 16777216.); - vec3 c = vec3(0.); - c.b = mod(value, 256.); - value = floor(value/256.); - c.g = mod(value, 256.); - value = floor(value/256.); - c.r = mod(value, 256.); - return vec4(c/255., 1.); -} - -#pragma glslify: export(encodeFloatRGBA) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/encode-id-rgb.glsl b/src/mol-gl/shader/utils/encode-id-rgb.glsl new file mode 100644 index 0000000000000000000000000000000000000000..5dd156c096e8ebf1506c8764564a61d015ee74e2 --- /dev/null +++ b/src/mol-gl/shader/utils/encode-id-rgb.glsl @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +#pragma glslify: encodeFloatRGB = require(../utils/encode-float-rgb.glsl) + +vec3 encodeIdRGB(const in float v) { + return encodeFloatRGB(v + 1.0); +} + +#pragma glslify: export(encodeIdRGB) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/encode-id-rgba.glsl b/src/mol-gl/shader/utils/encode-id-rgba.glsl deleted file mode 100644 index 9a47039656c6e0e0e8e0d08b8f411d3f6f3b89eb..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/utils/encode-id-rgba.glsl +++ /dev/null @@ -1,7 +0,0 @@ -#pragma glslify: encodeFloatRGBA = require(../utils/encode-float-rgba.glsl) - -vec4 encodeIdRGBA(const in float v) { - return encodeFloatRGBA(v + 1.0); -} - -#pragma glslify: export(encodeIdRGBA) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/read-from-texture.glsl b/src/mol-gl/shader/utils/read-from-texture.glsl index a43c9d528af35cd1a2be41dfb2d18d3b2c240bf1..7442b957dcd212f310fd8090102c618093f29a44 100644 --- a/src/mol-gl/shader/utils/read-from-texture.glsl +++ b/src/mol-gl/shader/utils/read-from-texture.glsl @@ -4,9 +4,12 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +float intDiv(float a, float b) { return float(int(a) / int(b)); } +float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } + vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 dim) { - float x = mod(i, dim.x); - float y = floor(i / dim.x); + float x = intMod(i, dim.x); + float y = floor(intDiv(i, dim.x)); vec2 uv = (vec2(x, y) + 0.5) / dim; return texture2D(tex, uv); } diff --git a/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl b/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl new file mode 100644 index 0000000000000000000000000000000000000000..d0d4f928722704f05356bb122005853f55cac4b1 --- /dev/null +++ b/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Michael Krone <michael.krone@uni-tuebingen.de> + */ + +float intDiv(float a, float b) { return float(int(a) / int(b)); } +float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } + +vec4 texture3dFrom2dLinear(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) { + float zSlice0 = floor(pos.z * gridDim.z); + float column0 = intMod(zSlice0 * gridDim.x, texDim.x) / gridDim.x; + float row0 = floor(intDiv(zSlice0 * gridDim.x, texDim.x)); + vec2 coord0 = (vec2(column0 * gridDim.x, row0 * gridDim.y) + (pos.xy * gridDim.xy)) / texDim; + vec4 color0 = texture2D(tex, coord0); + + float zSlice1 = zSlice0 + 1.0; + float column1 = intMod(zSlice1 * gridDim.x, texDim.x) / gridDim.x; + float row1 = floor(intDiv(zSlice1 * gridDim.x, texDim.x)); + vec2 coord1 = (vec2(column1 * gridDim.x, row1 * gridDim.y) + (pos.xy * gridDim.xy)) / texDim; + vec4 color1 = texture2D(tex, coord1); + + float delta0 = abs((pos.z * gridDim.z) - zSlice0); + return mix(color0, color1, delta0); +} + +#pragma glslify: export(texture3dFrom2dLinear) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl b/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl new file mode 100644 index 0000000000000000000000000000000000000000..8c4fb0882c831709358714535f7c24be8637e4a9 --- /dev/null +++ b/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Michael Krone <michael.krone@uni-tuebingen.de> + */ + +float intDiv(float a, float b) { return float(int(a) / int(b)); } +float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } + +vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) { + float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice + float column = intMod(zSlice * gridDim.x, texDim.x) / gridDim.x; + float row = floor(intDiv(zSlice * gridDim.x, texDim.x)); + vec2 coord = (vec2(column * gridDim.x, row * gridDim.y) + (pos.xy * gridDim.xy)) / texDim; + return texture2D(tex, coord); +} + +#pragma glslify: export(texture3dFrom2dNearest) \ No newline at end of file diff --git a/src/mol-gl/webgl/compat.ts b/src/mol-gl/webgl/compat.ts index f0bad2f5f48e7991d4393afa51da63f92d5de86b..7d763614ea33e22cf2c1c5ec32b8d5b85fb64ce2 100644 --- a/src/mol-gl/webgl/compat.ts +++ b/src/mol-gl/webgl/compat.ts @@ -106,4 +106,26 @@ export interface COMPAT_texture_float_linear { export function getTextureFloatLinear(gl: GLRenderingContext): COMPAT_texture_float_linear | null { return gl.getExtension('OES_texture_float_linear') +} + +export interface COMPAT_blend_minmax { + readonly MIN: number + readonly MAX: number +} + +export function getBlendMinMax(gl: GLRenderingContext): COMPAT_blend_minmax | null { + if (isWebGL2(gl)) { + return { MIN: gl.MIN, MAX: gl.MAX } + } else { + const ext = gl.getExtension('EXT_blend_minmax') + if (ext === null) return null + return { MIN: ext.MIN_EXT, MAX: ext.MAX_EXT } + } +} + +export interface COMPAT_frag_depth { +} + +export function getFragDepth(gl: GLRenderingContext): COMPAT_frag_depth | null { + return isWebGL2(gl) ? {} : gl.getExtension('EXT_frag_depth') } \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index 869526c5fe3c76e8364ccb09c8a9c312502b1f3b..93924a73f7ee88ff24895b4c27d603cd45728dee 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -6,7 +6,7 @@ import { createProgramCache, ProgramCache } from './program' import { createShaderCache, ShaderCache } from './shader' -import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear } from './compat'; +import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth } from './compat'; export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { @@ -100,10 +100,12 @@ export function createImageData(buffer: ArrayLike<number>, width: number, height type Extensions = { instancedArrays: COMPAT_instanced_arrays standardDerivatives: COMPAT_standard_derivatives - textureFloat: COMPAT_texture_float, - textureFloatLinear: COMPAT_texture_float_linear, + blendMinMax: COMPAT_blend_minmax + textureFloat: COMPAT_texture_float + textureFloatLinear: COMPAT_texture_float_linear elementIndexUint: COMPAT_element_index_uint | null vertexArrayObject: COMPAT_vertex_array_object | null + fragDepth: COMPAT_frag_depth | null } /** A WebGL context object, including the rendering context, resource caches and counts */ @@ -144,6 +146,10 @@ export function createContext(gl: GLRenderingContext): Context { if (standardDerivatives === null) { throw new Error('Could not find support for "standard_derivatives"') } + const blendMinMax = getBlendMinMax(gl) + if (blendMinMax === null) { + throw new Error('Could not find support for "blend_minmax"') + } const textureFloat = getTextureFloat(gl) if (textureFloat === null) { throw new Error('Could not find support for "texture_float"') @@ -160,6 +166,10 @@ export function createContext(gl: GLRenderingContext): Context { if (vertexArrayObject === null) { console.log('Could not find support for "vertex_array_object"') } + const fragDepth = getFragDepth(gl) + if (fragDepth === null) { + console.log('Could not find support for "frag_depth"') + } const shaderCache = createShaderCache() const programCache = createProgramCache() @@ -167,6 +177,11 @@ export function createContext(gl: GLRenderingContext): Context { const parameters = { maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE), maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) : 0, + maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS), + } + + if (parameters.maxVertexTextureImageUnits < 4) { + throw new Error('Need "MAX_VERTEX_TEXTURE_IMAGE_UNITS" >= 4') } return { @@ -175,10 +190,12 @@ export function createContext(gl: GLRenderingContext): Context { extensions: { instancedArrays, standardDerivatives, + blendMinMax, textureFloat, textureFloatLinear, elementIndexUint, - vertexArrayObject + vertexArrayObject, + fragDepth }, pixelRatio: getPixelRatio(), diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index ec3d687d23fba19bbe89a1d09ed2e00ce6b87a60..d2f59286b1510bfa5a39281e59e9b6373d50a6e4 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -227,8 +227,8 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S Object.keys(textureValues).forEach(k => { const value = textureValues[k] if (value.ref.version !== versions[k]) { - // update of textures with kind 'texture2d' or 'texture3d' is done externally - if (schema[k].kind !== 'texture2d' && schema[k].kind !== 'texture3d') { + // update of textures with kind 'texture' is done externally + if (schema[k].kind !== 'texture') { // console.log('texture version changed, uploading image', k) textures[k].load(value.ref.value as TextureImage<any> | TextureVolume<any>) versions[k] = value.ref.version @@ -246,8 +246,8 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S deleteVertexArray(ctx, vertexArrays[k]) }) Object.keys(textures).forEach(k => { - // lifetime of textures with kind 'texture2d' or 'texture3d' is defined externally - if (schema[k].kind !== 'texture2d' && schema[k].kind !== 'texture3d') { + // lifetime of textures with kind 'texture' is defined externally + if (schema[k].kind !== 'texture') { textures[k].destroy() } }) diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index e51497592f8d242be50a2ab3b594afdbac5229c5..c7bbe7ab70fe380ebb94b67fb69727674229765d 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -19,8 +19,7 @@ export type TextureKindValue = { 'image-float32': TextureImage<Float32Array> 'volume-uint8': TextureVolume<Uint8Array> 'volume-float32': TextureVolume<Float32Array> - 'texture2d': Texture - 'texture3d': Texture + 'texture': Texture } export type TextureValueType = Helpers.ValueOf<TextureKindValue> export type TextureKind = keyof TextureKindValue @@ -35,13 +34,11 @@ export function getTarget(ctx: Context, kind: TextureKind): number { switch (kind) { case 'image-uint8': return gl.TEXTURE_2D case 'image-float32': return gl.TEXTURE_2D - case 'texture2d': return gl.TEXTURE_2D } if (isWebGL2(gl)) { switch (kind) { case 'volume-uint8': return gl.TEXTURE_3D case 'volume-float32': return gl.TEXTURE_3D - case 'texture3d': return gl.TEXTURE_3D } } throw new Error('unknown texture kind') @@ -252,7 +249,7 @@ export function createTextures(ctx: Context, schema: RenderableSchema, values: T Object.keys(schema).forEach((k, i) => { const spec = schema[k] if (spec.type === 'texture') { - if (spec.kind === 'texture2d' || spec.kind === 'texture3d') { + if (spec.kind === 'texture') { textures[k] = values[k].ref.value as Texture } else { const texture = createTexture(ctx, spec.kind, spec.format, spec.dataType, spec.filter) diff --git a/src/mol-io/reader/_spec/ccp4.spec.ts b/src/mol-io/reader/_spec/ccp4.spec.ts index 0b3a23859da692e417edb8c2975bdcc881ef5813..fc57da2ad58e4c009ed85cc338dbf22b557c2e81 100644 --- a/src/mol-io/reader/_spec/ccp4.spec.ts +++ b/src/mol-io/reader/_spec/ccp4.spec.ts @@ -7,7 +7,7 @@ import CCP4 from '../ccp4/parser' import { FileHandle } from '../../common/file-handle'; -const ccp4Buffer = new Uint8Array([]) +const ccp4Buffer = new Uint8Array(4 * 64) describe('ccp4 reader', () => { it('basic', async () => { diff --git a/src/mol-io/reader/ccp4/parser.ts b/src/mol-io/reader/ccp4/parser.ts index 488ca23311375f5ac452e7a2a233b74fec102891..629303b84d2858f57fc18d19b4550f274176c228 100644 --- a/src/mol-io/reader/ccp4/parser.ts +++ b/src/mol-io/reader/ccp4/parser.ts @@ -8,7 +8,6 @@ import { Task, RuntimeContext } from 'mol-task'; import * as Schema from './schema' import Result from '../result' import { FileHandle } from '../../common/file-handle'; -import { flipByteOrder } from '../../common/binary'; async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Schema.Ccp4File>> { await ctx.update({ message: 'Parsing CCP4 file...' }); @@ -32,9 +31,12 @@ async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Res // 54 MACHST Machine stamp indicating machine type which wrote file // 17 and 17 for big-endian or 68 and 65 for little-endian const MACHST = [ dv.getUint8(53 * 4), dv.getUint8(53 * 4 + 1) ] - - if (MACHST[ 0 ] === 17 && MACHST[ 1 ] === 17) { - flipByteOrder(buffer, buffer.length) + // found MRC files that don't have the MACHST stamp set and are big-endian + if (MACHST[0] !== 68 && MACHST[1] !== 65) { + // flip byte order in-place + for (let i = 0, il = bin.byteLength; i < il; i += 4) { + dv.setFloat32(i, dv.getFloat32(i), true) + } } const header: Schema.Ccp4Header = { @@ -91,17 +93,13 @@ async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Res // TODO bytes 57-256 LABEL } + const offset = 256 * 4 + header.NSYMBT + const count = header.NC * header.NR * header.NS let values if (header.MODE === 2) { - values = new Float32Array( - bin, 256 * 4 + header.NSYMBT, - header.NX * header.NY * header.NZ - ) + values = new Float32Array(bin, offset, count) } else if (header.MODE === 0) { - values = new Float32Array(new Int8Array( - bin, 256 * 4 + header.NSYMBT, - header.NX * header.NY * header.NZ - )) + values = new Int8Array(bin, offset, count) } else { return Result.error(`ccp4 mode '${header.MODE}' unsupported`); } diff --git a/src/mol-io/reader/ccp4/schema.ts b/src/mol-io/reader/ccp4/schema.ts index 12b23631614eff9db792686a495c68e4a83e14f0..0ef828cf3b26e05ea79d7ce954f3f610770fa9cc 100644 --- a/src/mol-io/reader/ccp4/schema.ts +++ b/src/mol-io/reader/ccp4/schema.ts @@ -6,11 +6,11 @@ */ export interface Ccp4Header { - /** columns (fastest changing) */ + /** number of columns (fastest changing) */ NC: number - /** rows */ + /** number of rows */ NR: number - /** sections (slowest changing) */ + /** number of sections (slowest changing) */ NS: number /** * 0 image : signed 8-bit bytes range -128 to 127 @@ -49,11 +49,11 @@ export interface Ccp4Header { beta: number /** gamma cell angle (Degrees) */ gamma: number - /** axis corresponds for columns (1,2,3 for X,Y,Z) */ + /** axis corresponds to columns (1,2,3 for X,Y,Z) */ MAPC: number - /** axis corresponds for rows (1,2,3 for X,Y,Z) */ + /** axis corresponds to rows (1,2,3 for X,Y,Z) */ MAPR: number - /** axis corresponds for sections (1,2,3 for X,Y,Z) */ + /** axis corresponds to sections (1,2,3 for X,Y,Z) */ MAPS: number /** minimum density value */ AMIN: number @@ -67,8 +67,8 @@ export interface Ccp4Header { NSYMBT: number /** flag for skew transformation, =0 none, =1 if foll */ LSKFLG: number - /** Skew matrix S (in order S11, S12, S13, S21 etc) if LSKFLG .ne. 0 - * + /** + * Skew matrix S (in order S11, S12, S13, S21 etc) if LSKFLG .ne. 0 * May be used in CCP4 but not in MRC */ SKWMAT: number[] diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index 259d3ff576a1acd61ff554f271dfa287bca332b7..2c8a8ddda83090c2ac8a4f1e6675d3aaeb99b2a1 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -13,34 +13,36 @@ import { OrderedSet } from 'mol-data/int' import { Vec3, Tensor, Mat4 } from '../../linear-algebra' import { GaussianDensityValues } from 'mol-gl/renderable/gaussian-density' import { ValueCell, defaults } from 'mol-util' -import { RenderableState } from 'mol-gl/renderable' +import { RenderableState, Renderable } from 'mol-gl/renderable' import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object' import { Context, createContext, getGLContext } from 'mol-gl/webgl/context'; import { createFramebuffer } from 'mol-gl/webgl/framebuffer'; -import { createTexture, Texture, TextureAttachment } from 'mol-gl/webgl/texture'; +import { createTexture, Texture } from 'mol-gl/webgl/texture'; import { GLRenderingContext } from 'mol-gl/webgl/compat'; +import { decodeIdRGB } from 'mol-geo/geometry/picking'; export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> { const webgl = defaults(props.webgl, getWebGLContext()) + // always use texture2d when the gaussian density needs to be downloaded from the GPU, + // it's faster than texture3d + console.time('GaussianDensityTexture2d') + const { scale, bbox, texture, dim } = await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props) + console.timeEnd('GaussianDensityTexture2d') + const { field, idField } = fieldFromTexture2d(webgl, texture, dim) - const { transform, texture, gridDimension } = await GaussianDensityTexture(ctx, webgl, position, box, radius, props) - - const field = webgl.maxDrawBuffers > 0 ? - fieldFromTexture3d(webgl, texture, gridDimension) : - fieldFromTexture2d(webgl, texture, gridDimension) - - const idData = field.space.create() - const idField = Tensor.create(field.space, idData) + const transform = Mat4.identity() + Mat4.fromScaling(transform, scale) + Mat4.setTranslation(transform, bbox.min) return { field, idField, transform } } export async function GaussianDensityTexture(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): Promise<DensityTextureData> { - console.time(`GaussianDensityTexture, ${webgl.maxDrawBuffers > 0 ? 'multi' : 'single'}`) - const { texture, scale, bbox, dim } = webgl.maxDrawBuffers > 0 ? - await GaussianDensityMultiDrawBuffer(ctx, webgl, position, box, radius, props, oldTexture) : - await GaussianDensitySingleDrawBuffer(ctx, webgl, position, box, radius, props, oldTexture) - console.timeEnd(`GaussianDensityTexture, ${webgl.maxDrawBuffers > 0 ? 'multi' : 'single'}`) + console.time(`GaussianDensityTexture, ${webgl.isWebGL2 ? '3d' : '2d'}`) + const { texture, scale, bbox, dim } = webgl.isWebGL2 ? + await GaussianDensityTexture3d(ctx, webgl, position, box, radius, props, oldTexture) : + await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props, oldTexture) + console.timeEnd(`GaussianDensityTexture, ${webgl.isWebGL2 ? '3d' : '2d'}`) const transform = Mat4.identity() Mat4.fromScaling(transform, scale) @@ -51,29 +53,18 @@ export async function GaussianDensityTexture(ctx: RuntimeContext, webgl: Context // -async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) { +async function GaussianDensityTexture2d(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) { const { smoothness } = props - const { drawCount, positions, radii, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props) + const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props) const [ dx, dy, dz ] = dim - const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, expandedBox, dim, smoothness) - const renderable = createRenderable(webgl, renderObject) + const { texDimX, texDimY, texCols } = getTexture2dSize(webgl.maxTextureSize, dim) - // + const minDistanceTexture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest') + minDistanceTexture.define(texDimX, texDimY) - const maxTexSize = webgl.maxTextureSize - let fboTexDimX = 0 - let fboTexDimY = dim[1] - let fboTexRows = 1 - let fboTexCols = dim[0] - if (maxTexSize < dim[0] * dim[2]) { - fboTexCols = Math.floor(maxTexSize / dim[0]) - fboTexRows = Math.ceil(dim[2] / fboTexCols) - fboTexDimX = fboTexCols * dim[0] - fboTexDimY *= fboTexRows - } else { - fboTexDimX = dim[0] * dim[2] - } + const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness) + const renderable = createRenderable(webgl, renderObject) // @@ -82,35 +73,41 @@ async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Conte const framebuffer = createFramebuffer(webgl) framebuffer.bind() - - if (!texture) { - texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear') - } - texture.define(fboTexDimX, fboTexDimY) - - const program = renderable.getProgram('draw') - program.use() setRenderingDefaults(gl) - texture.attachFramebuffer(framebuffer, 0) - let currCol = 0 - let currY = 0 - let currX = 0 - for (let i = 0; i < dz; ++i) { - if (currCol >= fboTexCols) { - currCol -= fboTexCols - currY += dy - currX = 0 + if (!texture) texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear') + texture.define(texDimX, texDimY) + + function render(fbTex: Texture) { + fbTex.attachFramebuffer(framebuffer, 0) + let currCol = 0 + let currY = 0 + let currX = 0 + for (let i = 0; i < dz; ++i) { + if (currCol >= texCols) { + currCol -= texCols + currY += dy + currX = 0 + } + gl.viewport(currX, currY, dx, dy) + ValueCell.update(uCurrentSlice, i) + ValueCell.update(uCurrentX, currX) + ValueCell.update(uCurrentY, currY) + renderable.render('draw') + ++currCol + currX += dx } - gl.viewport(currX, currY, dx, dy) - ValueCell.update(uCurrentSlice, i) - ValueCell.update(uCurrentX, currX) - ValueCell.update(uCurrentY, currY) - renderable.render('draw') - ++currCol - currX += dx } + setupMinDistanceRendering(webgl, renderable) + render(minDistanceTexture) + + setupDensityRendering(webgl, renderable) + render(texture) + + setupGroupIdRendering(webgl, renderable) + render(texture) + framebuffer.destroy() // clean up await ctx.update({ message: 'gpu gaussian density calculation' }); @@ -119,61 +116,46 @@ async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Conte return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim } } -async function GaussianDensityMultiDrawBuffer(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) { +async function GaussianDensityTexture3d(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) { const { smoothness } = props - const { drawCount, positions, radii, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props) + const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props) const [ dx, dy, dz ] = dim - const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, expandedBox, dim, smoothness) + const minDistanceTexture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest') + minDistanceTexture.define(dx, dy, dz) + + const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness) const renderable = createRenderable(webgl, renderObject) - const drawBuffers = Math.min(8, webgl.maxDrawBuffers) // - const gl = webgl.gl as WebGL2RenderingContext + const { gl } = webgl const { uCurrentSlice } = renderObject.values const framebuffer = createFramebuffer(webgl) framebuffer.bind() - - setDrawBuffers(gl, drawBuffers) - gl.viewport(0, 0, dx, dy) setRenderingDefaults(gl) + gl.viewport(0, 0, dx, dy) - if (!texture) { - texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear') - } + if (!texture) texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear') texture.define(dx, dy, dz) - // z-slices to be render with multi render targets - const dzMulti = Math.floor(dz / drawBuffers) * drawBuffers - - // render multi target - const programMulti = renderable.getProgram('draw') - programMulti.use() - for (let i = 0; i < dzMulti; i += drawBuffers) { - ValueCell.update(uCurrentSlice, i) - for (let k = 0; k < drawBuffers; ++k) { - texture.attachFramebuffer(framebuffer, k as TextureAttachment, i + k) + function render(fbTex: Texture) { + for (let i = 0; i < dz; ++i) { + ValueCell.update(uCurrentSlice, i) + fbTex.attachFramebuffer(framebuffer, 0, i) + renderable.render('draw') } - renderable.render('draw'); } - // render single target - ValueCell.updateIfChanged(renderable.values.dDrawBuffers, 1) - renderable.update() - const programSingle = renderable.getProgram('draw') - programSingle.use() - for (let i = dzMulti; i < dz; ++i) { - ValueCell.update(uCurrentSlice, i) - texture.attachFramebuffer(framebuffer, 0, i) - renderable.render('draw') - } + setupMinDistanceRendering(webgl, renderable) + render(minDistanceTexture) - // must detach framebuffer attachments before reading is possible - for (let k = 0; k < drawBuffers; ++k) { - texture.detachFramebuffer(framebuffer, k as TextureAttachment) - } + setupDensityRendering(webgl, renderable) + render(texture) + + setupGroupIdRendering(webgl, renderable) + render(texture) framebuffer.destroy() // clean up @@ -208,6 +190,7 @@ async function prepareGaussianDensityData(ctx: RuntimeContext, position: Positio const positions = new Float32Array(n * 3) const radii = new Float32Array(n) + const groups = new Float32Array(n) let maxRadius = 0 @@ -220,13 +203,13 @@ async function prepareGaussianDensityData(ctx: RuntimeContext, position: Positio const r = radius(j) + radiusOffset if (maxRadius < r) maxRadius = r radii[i] = r + groups[i] = i if (i % 10000 === 0 && ctx.shouldUpdate) { await ctx.update({ message: 'preparing density data', current: i, max: n }) } } - const pad = maxRadius * 2 + resolution const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad)); const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min) @@ -236,11 +219,12 @@ async function prepareGaussianDensityData(ctx: RuntimeContext, position: Positio Vec3.ceil(dim, Vec3.mul(dim, extent, delta)) console.log('grid dim gpu', dim) - return { drawCount: n, positions, radii, delta, expandedBox, dim } + return { drawCount: n, positions, radii, groups, delta, expandedBox, dim } } -function getGaussianDensityRenderObject(webgl: Context, drawCount: number, positions: Float32Array, radii: Float32Array, box: Box3D, dimensions: Vec3, smoothness: number) { +function getGaussianDensityRenderObject(webgl: Context, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, dimensions: Vec3, smoothness: number) { const extent = Vec3.sub(Vec3.zero(), box.max, box.min) + const { texDimX, texDimY } = getTexture2dSize(webgl.maxTextureSize, dimensions) const values: GaussianDensityValues = { drawCount: ValueCell.create(drawCount), @@ -248,6 +232,7 @@ function getGaussianDensityRenderObject(webgl: Context, drawCount: number, posit aRadius: ValueCell.create(radii), aPosition: ValueCell.create(positions), + aGroup: ValueCell.create(groups), uCurrentSlice: ValueCell.create(0), uCurrentX: ValueCell.create(0), @@ -256,9 +241,12 @@ function getGaussianDensityRenderObject(webgl: Context, drawCount: number, posit uBboxMax: ValueCell.create(box.max), uBboxSize: ValueCell.create(extent), uGridDim: ValueCell.create(dimensions), + uGridTexDim: ValueCell.create(Vec3.create(texDimX, texDimY, 0)), uAlpha: ValueCell.create(smoothness), + tMinDistanceTex: ValueCell.create(minDistanceTexture), - dDrawBuffers: ValueCell.create(Math.min(8, webgl.maxDrawBuffers)), + dGridTexType: ValueCell.create(minDistanceTexture.depth > 0 ? '3d' : '2d'), + dCalcType: ValueCell.create('density'), } const state: RenderableState = { visible: true, @@ -274,27 +262,52 @@ function setRenderingDefaults(gl: GLRenderingContext) { gl.disable(gl.CULL_FACE) gl.frontFace(gl.CCW) gl.cullFace(gl.BACK) + gl.enable(gl.BLEND) +} +function setupMinDistanceRendering(webgl: Context, renderable: Renderable<any>) { + const { gl } = webgl + ValueCell.update(renderable.values.dCalcType, 'minDistance') + renderable.update() + renderable.getProgram('draw').use() + gl.blendFunc(gl.ONE, gl.ONE) + // the shader writes 1 - dist so we set blending to MAX + gl.blendEquation(webgl.extensions.blendMinMax.MAX) +} + +function setupDensityRendering(webgl: Context, renderable: Renderable<any>) { + const { gl } = webgl + ValueCell.update(renderable.values.dCalcType, 'density') + renderable.update() + renderable.getProgram('draw').use() gl.blendFunc(gl.ONE, gl.ONE) gl.blendEquation(gl.FUNC_ADD) - gl.enable(gl.BLEND) } -function setDrawBuffers(gl: WebGL2RenderingContext, drawBuffers: number) { - if (drawBuffers === 1) { - gl.drawBuffers([ - gl.COLOR_ATTACHMENT0, - ]); - } else if (drawBuffers === 4) { - gl.drawBuffers([ - gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3, - ]); - } else if (drawBuffers === 8) { - gl.drawBuffers([ - gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3, - gl.COLOR_ATTACHMENT4, gl.COLOR_ATTACHMENT5, gl.COLOR_ATTACHMENT6, gl.COLOR_ATTACHMENT7, - ]); +function setupGroupIdRendering(webgl: Context, renderable: Renderable<any>) { + const { gl } = webgl + ValueCell.update(renderable.values.dCalcType, 'groupId') + renderable.update() + renderable.getProgram('draw').use() + // overwrite color, don't change alpha + gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ZERO, gl.ONE) + gl.blendEquation(gl.FUNC_ADD) +} + +function getTexture2dSize(maxTexSize: number, gridDim: Vec3) { + let texDimX = 0 + let texDimY = gridDim[1] + let texRows = 1 + let texCols = gridDim[0] + if (maxTexSize < gridDim[0] * gridDim[2]) { + texCols = Math.floor(maxTexSize / gridDim[0]) + texRows = Math.ceil(gridDim[2] / texCols) + texDimX = texCols * gridDim[0] + texDimY *= texRows + } else { + texDimX = gridDim[0] * gridDim[2] } + return { texDimX, texDimY, texRows, texCols } } function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) { @@ -307,6 +320,8 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) { const space = Tensor.Space(dim, [2, 1, 0], Float32Array) const data = space.create() const field = Tensor.create(space, data) + const idData = space.create() + const idField = Tensor.create(space, idData) const image = new Uint8Array(width * height * 4) @@ -316,7 +331,7 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) { texture.attachFramebuffer(framebuffer, 0) gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image) - let idx = 0 + let j = 0 let tmpCol = 0 let tmpRow = 0 for (let iz = 0; iz < dz; ++iz) { @@ -326,8 +341,10 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) { } for (let iy = 0; iy < dy; ++iy) { for (let ix = 0; ix < dx; ++ix) { - data[idx] = image[4 * (tmpCol * dx + (iy + tmpRow) * width + ix) + 3] / 255 - idx++ + const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix) + data[j] = image[idx + 3] / 255 + idData[j] = decodeIdRGB(image[idx], image[idx + 1], image[idx + 2]) + j++ } } tmpCol++ @@ -336,37 +353,41 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) { framebuffer.destroy() console.timeEnd('fieldFromTexture2d') - return field + return { field, idField } } -function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) { - console.time('fieldFromTexture3d') - const { gl } = ctx - const { width, height, depth } = texture - - const space = Tensor.Space(dim, [2, 1, 0], Float32Array) - const data = space.create() - const field = Tensor.create(space, data) - - const slice = new Uint8Array(width * height * 4) - - const framebuffer = createFramebuffer(ctx) - framebuffer.bind() - - let j = 0 - for (let i = 0; i < depth; ++i) { - texture.attachFramebuffer(framebuffer, 0, i) - gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice) - for (let iy = 0; iy < height; ++iy) { - for (let ix = 0; ix < width; ++ix) { - data[j] = slice[4 * (iy * width + ix) + 3] / 255 - ++j - } - } - } - - framebuffer.destroy() - console.timeEnd('fieldFromTexture3d') - - return field -} \ No newline at end of file +// function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) { +// console.time('fieldFromTexture3d') +// const { gl } = ctx +// const { width, height, depth } = texture + +// const space = Tensor.Space(dim, [2, 1, 0], Float32Array) +// const data = space.create() +// const field = Tensor.create(space, data) +// const idData = space.create() +// const idField = Tensor.create(space, idData) + +// const slice = new Uint8Array(width * height * 4) + +// const framebuffer = createFramebuffer(ctx) +// framebuffer.bind() + +// let j = 0 +// for (let i = 0; i < depth; ++i) { +// texture.attachFramebuffer(framebuffer, 0, i) +// gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice) +// for (let iy = 0; iy < height; ++iy) { +// for (let ix = 0; ix < width; ++ix) { +// const idx = 4 * (iy * width + ix) +// data[j] = slice[idx + 3] / 255 +// idData[j] = decodeIdRGB(slice[idx], slice[idx + 1], slice[idx + 2]) +// ++j +// } +// } +// } + +// framebuffer.destroy() +// console.timeEnd('fieldFromTexture3d') + +// return { field, idField } +// } \ No newline at end of file diff --git a/src/mol-model/volume/data.ts b/src/mol-model/volume/data.ts index 05e85eba318b1d8eec28f77ec161f0589524fefd..cda1035294766b00ea4edc8970c9bee925f1b95f 100644 --- a/src/mol-model/volume/data.ts +++ b/src/mol-model/volume/data.ts @@ -7,8 +7,8 @@ import { SpacegroupCell, Box3D } from 'mol-math/geometry' import { Tensor, Mat4, Vec3 } from 'mol-math/linear-algebra' +/** The basic unit cell that contains the data. */ interface VolumeData { - // The basic unit cell that contains the data. readonly cell: SpacegroupCell, readonly fractionalBox: Box3D, readonly data: Tensor, diff --git a/src/mol-model/volume/formats/density-server.ts b/src/mol-model/volume/formats/density-server.ts index 300d7039c56e329b2eea986c2753b9499225d5c1..0b1cb234dc0be00610f1636bfc5dded5ad3698e6 100644 --- a/src/mol-model/volume/formats/density-server.ts +++ b/src/mol-model/volume/formats/density-server.ts @@ -13,14 +13,17 @@ import { Tensor, Vec3 } from 'mol-math/linear-algebra'; function parseDensityServerData(source: DensityServer_Data_Database): Task<VolumeData> { return Task.create<VolumeData>('Parse Volume Data', async ctx => { const { volume_data_3d_info: info, volume_data_3d: values } = source; - const cell = SpacegroupCell.create(info.spacegroup_number.value(0), + const cell = SpacegroupCell.create( + info.spacegroup_number.value(0), Vec3.ofArray(info.spacegroup_cell_size.value(0)), - Vec3.scale(Vec3.zero(), Vec3.ofArray(info.spacegroup_cell_angles.value(0)), Math.PI / 180)); + Vec3.scale(Vec3.zero(), Vec3.ofArray(info.spacegroup_cell_angles.value(0)), Math.PI / 180) + ); const tensorSpace = Tensor.Space(info.sample_count.value(0), info.axis_order.value(0), Float32Array); const data = Tensor.create(tensorSpace, Tensor.Data1(values.values.toArray())); - const origin = Vec3.ofArray(info.origin.value(0)), dimensions = Vec3.ofArray(info.dimensions.value(0)); + const origin = Vec3.ofArray(info.origin.value(0)) + const dimensions = Vec3.ofArray(info.dimensions.value(0)); return { cell, diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 7d092a8370f2cd7520dad135573b5a99713dc140..aab76b56f79e2f5444ee06aea0bbf388e141ecb8 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -21,7 +21,7 @@ import { Representation } from 'mol-geo/representation'; import { createRenderTarget } from 'mol-gl/webgl/render-target'; import Scene from 'mol-gl/scene'; import { RenderVariant } from 'mol-gl/webgl/render-item'; -import { PickingId, decodeIdRGBA } from 'mol-geo/geometry/picking'; +import { PickingId, decodeIdRGB } from 'mol-geo/geometry/picking'; import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; import { Color } from 'mol-util/color'; @@ -231,15 +231,15 @@ namespace Viewer { objectPickTarget.bind() ctx.readPixels(xp, yp, 1, 1, buffer) - const objectId = decodeIdRGBA(buffer[0], buffer[1], buffer[2]) + const objectId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) instancePickTarget.bind() ctx.readPixels(xp, yp, 1, 1, buffer) - const instanceId = decodeIdRGBA(buffer[0], buffer[1], buffer[2]) + const instanceId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) groupPickTarget.bind() ctx.readPixels(xp, yp, 1, 1, buffer) - const groupId = decodeIdRGBA(buffer[0], buffer[1], buffer[2]) + const groupId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) isPicking = false