diff --git a/src/mol-geo/representation/structure/units-visual.ts b/src/mol-geo/representation/structure/units-visual.ts index 3f7a963ec67e96884b75331d9a12b3514bbeca40..5b58cc2ed35ef22d69875d92d709a592fc382f1c 100644 --- a/src/mol-geo/representation/structure/units-visual.ts +++ b/src/mol-geo/representation/structure/units-visual.ts @@ -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 + 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 +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 + 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 }, - 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: DirectVolume.updateValues + }) } \ No newline at end of file