-
Alexander Rose authoredAlexander Rose authored
isosurface.ts 7.83 KiB
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { VolumeData, VolumeIsoValue } from 'mol-model/volume'
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
import { EmptyLoci } from 'mol-model/loci';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { Mesh } from 'mol-geo/geometry/mesh/mesh';
import { computeMarchingCubesMesh, computeMarchingCubesLines } from 'mol-geo/util/marching-cubes/algorithm';
import { LocationIterator } from 'mol-geo/util/location-iterator';
import { VisualUpdateState } from 'mol-repr/util';
import { RepresentationContext, RepresentationParamsGetter, Representation } from 'mol-repr/representation';
import { Theme, ThemeRegistryContext } from 'mol-theme/theme';
import { VisualContext } from 'mol-repr/visual';
import { NullLocation } from 'mol-model/location';
import { Lines } from 'mol-geo/geometry/lines/lines';
const IsoValueParam = PD.Conditioned(
VolumeIsoValue.relative(2),
{
'absolute': PD.Converted(
(v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats).absoluteValue,
(v: number) => VolumeIsoValue.absolute(v),
PD.Numeric(0.5, { min: -1, max: 1, step: 0.01 })
),
'relative': PD.Converted(
(v: VolumeIsoValue) => VolumeIsoValue.toRelative(v, VolumeData.Empty.dataStats).relativeValue,
(v: number) => VolumeIsoValue.relative(v),
PD.Numeric(2, { min: -10, max: 10, step: 0.01 })
)
},
(v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
(v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats) : VolumeIsoValue.toRelative(v, VolumeData.Empty.dataStats)
)
type IsoValueParam = typeof IsoValueParam
export const VolumeIsosurfaceParams = {
isoValue: IsoValueParam
}
export type VolumeIsosurfaceParams = typeof VolumeIsosurfaceParams
export type VolumeIsosurfaceProps = PD.Values<VolumeIsosurfaceParams>
//
export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: VolumeData, theme: Theme, props: VolumeIsosurfaceProps, mesh?: Mesh) {
ctx.runtime.update({ message: 'Marching cubes...' });
const surface = await computeMarchingCubesMesh({
isoLevel: VolumeIsoValue.toAbsolute(props.isoValue, volume.dataStats).absoluteValue,
scalarField: volume.data
}, mesh).runAsChild(ctx.runtime);
const transform = VolumeData.getGridToCartesianTransform(volume);
ctx.runtime.update({ message: 'Transforming mesh...' });
Mesh.transformImmediate(surface, transform);
Mesh.computeNormalsImmediate(surface)
return surface;
}
export const IsosurfaceMeshParams = {
...Mesh.Params,
...VolumeIsosurfaceParams
}
export type IsosurfaceMeshParams = typeof IsosurfaceMeshParams
export function IsosurfaceMeshVisual(): VolumeVisual<IsosurfaceMeshParams> {
return VolumeVisual<Mesh, IsosurfaceMeshParams>({
defaultProps: PD.getDefaultValues(IsosurfaceMeshParams),
createGeometry: createVolumeIsosurfaceMesh,
createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
getLoci: () => EmptyLoci,
mark: () => false,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
if (newProps.isoValue !== currentProps.isoValue) state.createGeometry = true
},
geometryUtils: Mesh.Utils
})
}
//
export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume: VolumeData, theme: Theme, props: VolumeIsosurfaceProps, lines?: Lines) {
ctx.runtime.update({ message: 'Marching cubes...' });
const wireframe = await computeMarchingCubesLines({
isoLevel: VolumeIsoValue.toAbsolute(props.isoValue, volume.dataStats).absoluteValue,
scalarField: volume.data
}, lines).runAsChild(ctx.runtime)
const transform = VolumeData.getGridToCartesianTransform(volume);
Lines.transformImmediate(wireframe, transform)
return wireframe;
}
export const IsosurfaceWireframeParams = {
...Lines.Params,
...VolumeIsosurfaceParams
}
export type IsosurfaceWireframeParams = typeof IsosurfaceWireframeParams
export function IsosurfaceWireframeVisual(): VolumeVisual<IsosurfaceWireframeParams> {
return VolumeVisual<Lines, IsosurfaceWireframeParams>({
defaultProps: PD.getDefaultValues(IsosurfaceWireframeParams),
createGeometry: createVolumeIsosurfaceWireframe,
createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
getLoci: () => EmptyLoci,
mark: () => false,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IsosurfaceWireframeParams>, currentProps: PD.Values<IsosurfaceWireframeParams>) => {
if (newProps.isoValue !== currentProps.isoValue) state.createGeometry = true
},
geometryUtils: Lines.Utils
})
}
//
const IsosurfaceVisuals = {
'solid': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceMeshParams>) => VolumeRepresentation('Isosurface mesh', ctx, getParams, IsosurfaceMeshVisual),
'wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceWireframeParams>) => VolumeRepresentation('Isosurface wireframe', ctx, getParams, IsosurfaceWireframeVisual),
}
type IsosurfaceVisualName = keyof typeof IsosurfaceVisuals
const IsosurfaceVisualOptions = Object.keys(IsosurfaceVisuals).map(name => [name, name] as [IsosurfaceVisualName, string])
export const IsosurfaceParams = {
...IsosurfaceMeshParams,
...IsosurfaceWireframeParams,
visuals: PD.MultiSelect<IsosurfaceVisualName>(['solid'], IsosurfaceVisualOptions),
}
export type IsosurfaceParams = typeof IsosurfaceParams
export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeData) {
const p = PD.clone(IsosurfaceParams)
const stats = volume.dataStats
const { min, max, mean, sigma } = stats
p.isoValue = PD.Conditioned(
VolumeIsoValue.relative(2),
{
'absolute': PD.Converted(
(v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v, stats).absoluteValue,
(v: number) => VolumeIsoValue.absolute(v),
PD.Numeric(mean, { min, max, step: sigma / 100 })
),
'relative': PD.Converted(
(v: VolumeIsoValue) => VolumeIsoValue.toRelative(v, stats).relativeValue,
(v: number) => VolumeIsoValue.relative(v),
PD.Numeric(2, { min: -10, max: 10, step: 0.001 })
)
},
(v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
(v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, stats) : VolumeIsoValue.toRelative(v, stats)
)
return p
}
export type IsosurfaceRepresentation = VolumeRepresentation<IsosurfaceParams>
export function IsosurfaceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceParams>): IsosurfaceRepresentation {
return Representation.createMulti('Isosurface', ctx, getParams, Representation.StateBuilder, IsosurfaceVisuals as unknown as Representation.Def<VolumeData, IsosurfaceParams>)
}
export const IsosurfaceRepresentationProvider: VolumeRepresentationProvider<IsosurfaceParams> = {
label: 'Isosurface',
description: 'Displays an isosurface of volumetric data.',
factory: IsosurfaceRepresentation,
getParams: getIsosurfaceParams,
defaultValues: PD.getDefaultValues(IsosurfaceParams),
defaultColorTheme: 'uniform',
defaultSizeTheme: 'uniform'
}