Skip to content
Snippets Groups Projects
Commit f68d16b9 authored by Alexander Rose's avatar Alexander Rose
Browse files

simplified units-visual builder

parent 02892d7d
No related branches found
No related tags found
No related merge requests found
......@@ -4,11 +4,9 @@
* @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, StructureProps, StructureParams } from '.';
import { RuntimeContext } from 'mol-task';
import { PickingId } from '../../geometry/picking';
import { LocationIterator } from '../../util/location-iterator';
......@@ -26,6 +24,7 @@ import { createSizes, SizeProps } from '../../geometry/size-data';
import { Lines } from '../../geometry/lines/lines';
import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter';
import { DirectVolume } from '../../geometry/direct-volume/direct-volume';
import { RenderableValues } from 'mol-gl/renderable/schema';
export const UnitKindInfo = {
'atomic': {},
......@@ -72,8 +71,13 @@ function colorChanged(oldProps: ColorProps, newProps: ColorProps) {
}
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> {
defaultProps: P
......@@ -84,23 +88,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 StructureProps, 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 +113,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 +140,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 +154,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 +232,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 +266,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
},
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: Points.createEmpty,
createRenderObject: createUnitsPointsRenderObject,
setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
builder.setUpdateState(state, newProps, currentProps)
if (sizeChanged(currentProps, newProps)) state.updateSize = true
},
destroy() {
// TODO
renderObject = undefined
}
}
updateValues: Points.updateValues
})
}
// lines
......@@ -383,139 +289,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
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
},
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: Lines.updateValues
})
}
// direct-volume
......@@ -529,137 +312,14 @@ export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps
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, mark, setUpdateState } = builder
const updateState = VisualUpdateState.create()
let renderObject: DirectVolumeRenderObject | undefined
let currentProps: P
let directVolume: DirectVolume
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)
: DirectVolume.createEmpty(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)
: DirectVolume.createEmpty(directVolume)
updateState.updateColor = true
}
if (updateState.updateColor) {
await createColors(ctx, locationIt, newProps, renderObject.values)
}
DirectVolume.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)
}
},
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: DirectVolume.createEmpty,
createRenderObject: createUnitsDirectVolumeRenderObject,
setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
builder.setUpdateState(state, newProps, currentProps)
if (sizeChanged(currentProps, newProps)) state.createGeometry = true
},
destroy() {
// TODO
renderObject = undefined
}
}
updateValues: DirectVolume.updateValues
})
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment