diff --git a/src/apps/viewer/index.tsx b/src/apps/viewer/index.tsx index b4d3823d0bc0d8f2fe473fc73cf2e33b5b4ba2f6..940efc7d074991890d466e3ec52e10da2fa25c69 100644 --- a/src/apps/viewer/index.tsx +++ b/src/apps/viewer/index.tsx @@ -23,6 +23,9 @@ import { EntityTreeController } from 'mol-app/controller/entity/tree'; import { TransformListController } from 'mol-app/controller/transform/list'; import { TransformList } from 'mol-app/ui/transform/list'; import { SequenceView } from 'mol-app/ui/visualization/sequence-view'; +import { InteractivityEvents } from 'mol-app/event/basic'; +import { MarkerAction } from 'mol-geo/util/marker-data'; +import { EveryLoci } from 'mol-model/loci'; const elm = document.getElementById('app') if (!elm) throw new Error('Can not find element with id "app".') @@ -94,4 +97,17 @@ ctx.layout.setState({ }) // ctx.viewport.setState() +ctx.dispatcher.getStream(InteractivityEvents.HighlightLoci).subscribe(event => { + ctx.stage.viewer.mark(EveryLoci, MarkerAction.RemoveHighlight) + if (event && event.data) { + ctx.stage.viewer.mark(event.data, MarkerAction.Highlight) + } +}) + +ctx.dispatcher.getStream(InteractivityEvents.SelectLoci).subscribe(event => { + if (event && event.data) { + ctx.stage.viewer.mark(event.data, MarkerAction.ToggleSelect) + } +}) + ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm); diff --git a/src/mol-app/event/basic.ts b/src/mol-app/event/basic.ts index b87ea5c14eec10f8c2a7ff7161cd569221611bde..f52d6e651296d7839c83388cfa827587949aa553 100644 --- a/src/mol-app/event/basic.ts +++ b/src/mol-app/event/basic.ts @@ -11,7 +11,7 @@ import { Dispatcher } from '../service/dispatcher' import { LayoutState } from '../controller/layout'; import { ViewportOptions } from '../controller/visualization/viewport'; import { Job } from '../service/job'; -import { Element } from 'mol-model/structure' +import { Loci } from 'mol-model/loci'; const Lane = Dispatcher.Lane; @@ -34,5 +34,7 @@ export namespace LayoutEvents { } export namespace InteractivityEvents { - export const HighlightElementLoci = Event.create<Element.Loci | undefined>('bs.Interactivity.HighlightElementLoci', Lane.Slow); + export const HighlightLoci = Event.create<Loci>('bs.Interactivity.HighlightLoci', Lane.Slow); + export const SelectLoci = Event.create<Loci>('bs.Interactivity.SelectLoci', Lane.Slow); + export const LabelLoci = Event.create<Loci>('bs.Interactivity.LabelLoci', Lane.Slow); } diff --git a/src/mol-app/ui/visualization/sequence-view.tsx b/src/mol-app/ui/visualization/sequence-view.tsx index 21bef90b8afe67226ea4cf62b799778664043b73..520505ca2df4992a7466a06491d39893321e8342 100644 --- a/src/mol-app/ui/visualization/sequence-view.tsx +++ b/src/mol-app/ui/visualization/sequence-view.tsx @@ -11,6 +11,7 @@ import { Structure, StructureSequence, Queries, Selection } from 'mol-model/stru import { Context } from '../../context/context'; import { InteractivityEvents } from '../../event/basic'; import { SyncRuntimeContext } from 'mol-task/execution/synchronous'; +import { EmptyLoci } from 'mol-model/loci'; export class SequenceView extends View<SequenceViewController, {}, {}> { render() { @@ -36,14 +37,14 @@ class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSeque async raiseInteractityEvent(seqId?: number) { if (typeof seqId === 'undefined') { - InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0); + InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci); return; } const query = createQuery(this.props.seq.entityId, seqId); const loci = Selection.toLoci(await query(this.props.structure, SyncRuntimeContext)); - if (loci.elements.length === 0) InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0); - else InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, loci); + if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci); + else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci); } diff --git a/src/mol-app/ui/visualization/viewport.tsx b/src/mol-app/ui/visualization/viewport.tsx index a13c12809066f2d3b9c790f5d9c082986e54e112..902882921bd471e896e290f71a0e8942bef3c8f1 100644 --- a/src/mol-app/ui/visualization/viewport.tsx +++ b/src/mol-app/ui/visualization/viewport.tsx @@ -14,6 +14,8 @@ import { View } from '../view'; import { HelpBox, Toggle, Button } from '../controls/common' import { Slider } from '../controls/slider' import { ImageCanvas } from './image-canvas'; +import { InteractivityEvents } from '../../event/basic'; +import { labelFirst } from 'mol-view/label'; export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> { state = { showSceneOptions: false, showHelp: false }; @@ -132,6 +134,7 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl? if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) { this.setState({ noWebGl: true }); } + this.handleResize() const viewer = this.controller.context.stage.viewer @@ -142,10 +145,6 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl? }) }) - viewer.identified.subscribe(info => { - this.setState({ info }) - }) - viewer.didDraw.subscribe(() => { // this.setState({ imageData: viewer.getImageData() }) this.setState({ @@ -158,7 +157,23 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl? }) viewer.input.resize.subscribe(() => this.handleResize()) - this.handleResize() + + viewer.input.move.subscribe(({x, y, inside}) => { + if (!inside) return + const p = viewer.identify(x, y) + const loci = viewer.getLoci(p) + InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci); + + // TODO use LabelLoci event and make configurable + const label = labelFirst(loci) + const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}` + this.setState({ info }) + }) + + viewer.input.click.subscribe(({x, y}) => { + const loci = viewer.getLoci(viewer.identify(x, y)) + InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci); + }) } componentWillUnmount() { diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index 7c2d2de3657ed3bdbbf102bf164848419dbd1a38..0f0e04bf4d2ffe42f775eaa07d542f1d55bb23b1 100644 --- a/src/mol-geo/representation/index.ts +++ b/src/mol-geo/representation/index.ts @@ -8,7 +8,7 @@ import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object' import { PickingId } from '../util/picking'; import { Loci } from 'mol-model/loci'; -import { FlagAction } from '../util/flag-data'; +import { MarkerAction } from '../util/marker-data'; export interface RepresentationProps {} @@ -16,6 +16,6 @@ export interface Representation<D, P extends RepresentationProps = {}> { renderObjects: ReadonlyArray<RenderObject> create: (data: D, props?: P) => Task<void> update: (props: P) => Task<void> - getLoci: (pickingId: PickingId) => Loci | null - applyFlags: (loci: Loci, action: FlagAction) => void + getLoci: (pickingId: PickingId) => Loci + mark: (loci: Loci, action: MarkerAction) => void } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/bond.ts b/src/mol-geo/representation/structure/bond.ts index d4a30eea3782caabe13842b41d3190757d578369..69ac5791127e0be2d94d17e7fdebd3445e4ce8b5 100644 --- a/src/mol-geo/representation/structure/bond.ts +++ b/src/mol-geo/representation/structure/bond.ts @@ -5,6 +5,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ +// TODO multiple cylinders for higher bond orders + import { ValueCell } from 'mol-util/value-cell' import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' @@ -21,8 +23,8 @@ import { MeshBuilder } from '../../shape/mesh-builder'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { createUniformColor } from '../../util/color-data'; import { defaults } from 'mol-util'; -import { Loci, isEveryLoci } from 'mol-model/loci'; -import { FlagAction, applyFlagAction, createFlags } from '../../util/flag-data'; +import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci'; +import { MarkerAction, applyMarkerAction, createMarkers } from '../../util/marker-data'; function createBondMesh(unit: Unit, mesh?: Mesh) { return Task.create('Cylinder mesh', async ctx => { @@ -110,15 +112,15 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps await ctx.update('Computing bond colors'); const color = createUniformColor({ value: 0xFF0000 }) - await ctx.update('Computing bond flags'); - const flag = createFlags(instanceCount * elementCount) + await ctx.update('Computing bond marks'); + const marker = createMarkers(instanceCount * elementCount) const values: MeshValues = { ...getMeshData(mesh), aTransform: transforms, aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), ...color, - ...flag, + ...marker, uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), uInstanceCount: ValueCell.create(instanceCount), @@ -132,6 +134,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)), dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)), dFlipSided: ValueCell.create(defaults(props.flipSided, false)), + dUseFog: ValueCell.create(defaults(props.useFog, true)), } const state: RenderableState = { depthMask: defaults(props.depthMask, true), @@ -171,11 +174,11 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps bIndex: unit.links.b[elementId] }]) } - return null + return EmptyLoci }, - applyFlags(loci: Loci, action: FlagAction) { + mark(loci: Loci, action: MarkerAction) { const group = currentGroup - const tFlag = cylinders.values.tFlag + const tMarker = cylinders.values.tMarker const unit = group.units[0] if (!Unit.isAtomic(unit)) return @@ -183,9 +186,9 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps const instanceCount = group.units.length let changed = false - const array = tFlag.ref.value.array + const array = tMarker.ref.value.array if (isEveryLoci(loci)) { - applyFlagAction(array, 0, elementCount * instanceCount, action) + applyMarkerAction(array, 0, elementCount * instanceCount, action) changed = true } else if (Link.isLoci(loci)) { for (const b of loci.links) { @@ -194,7 +197,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex) if (_idx !== -1) { const idx = _idx - if (applyFlagAction(array, idx, idx + 1, action) && !changed) { + if (applyMarkerAction(array, idx, idx + 1, action) && !changed) { changed = true } } @@ -204,7 +207,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps return } if (changed) { - ValueCell.update(tFlag, tFlag.ref.value) + ValueCell.update(tMarker, tMarker.ref.value) } } } diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 32dfa0a4bbadb1bf11e5f9a29a176adf9b614a73..4716fc2c4c123884a5d4e6c5104ea5845f81a30f 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -11,15 +11,15 @@ import { RenderObject } from 'mol-gl/render-object'; import { Representation, RepresentationProps } from '..'; import { ColorTheme } from '../../theme'; import { PickingId } from '../../util/picking'; -import { Loci } from 'mol-model/loci'; -import { FlagAction } from '../../util/flag-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { MarkerAction } from '../../util/marker-data'; export interface UnitsRepresentation<P> { renderObjects: ReadonlyArray<RenderObject> create: (group: Unit.SymmetryGroup, props: P) => Task<void> update: (props: P) => Task<boolean> - getLoci: (pickingId: PickingId) => Loci | null - applyFlags: (loci: Loci, action: FlagAction) => void + getLoci: (pickingId: PickingId) => Loci + mark: (loci: Loci, action: MarkerAction) => void } export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { } @@ -34,7 +34,8 @@ export const DefaultStructureProps = { alpha: 1, visible: true, doubleSided: false, - depthMask: true + depthMask: true, + useFog: true, } export type StructureProps = Partial<typeof DefaultStructureProps> @@ -48,7 +49,7 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () = const loc = groupReprs[i].repr.getLoci(pickingId) if (loc) return loc } - return null + return EmptyLoci } return { @@ -88,9 +89,9 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () = }) }, getLoci, - applyFlags(loci: Loci, action: FlagAction) { + mark(loci: Loci, action: MarkerAction) { for (let i = 0, il = groupReprs.length; i < il; ++i) { - groupReprs[i].repr.applyFlags(loci, action) + groupReprs[i].repr.mark(loci, action) } } } diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index 2daed89a676dc1330a36a4283b3b76d926222c42..9377973081a46f5f19adb1df1b0bbd987f56805c 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -14,13 +14,13 @@ import { fillSerial } from 'mol-gl/renderable/util'; import { UnitsRepresentation, DefaultStructureProps } from './index'; import VertexMap from '../../shape/vertex-map'; import { SizeTheme } from '../../theme'; -import { createTransforms, createColors, createSizes, applyElementFlags } from './utils'; +import { createTransforms, createColors, createSizes, markElement } from './utils'; import { deepEqual, defaults } from 'mol-util'; import { SortedArray } from 'mol-data/int'; import { RenderableState, PointValues } from 'mol-gl/renderable'; import { PickingId } from '../../util/picking'; -import { Loci } from 'mol-model/loci'; -import { FlagAction, createFlags } from '../../util/flag-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { MarkerAction, createMarkers } from '../../util/marker-data'; export const DefaultPointProps = { ...DefaultStructureProps, @@ -91,8 +91,8 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro await ctx.update('Computing point sizes'); const size = createSizes(group, vertexMap, sizeTheme) - await ctx.update('Computing spacefill flags'); - const flag = createFlags(instanceCount * elementCount) + await ctx.update('Computing spacefill marks'); + const marker = createMarkers(instanceCount * elementCount) const values: PointValues = { aPosition: ValueCell.create(vertices), @@ -100,7 +100,7 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro aTransform: transforms, aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), ...color, - ...flag, + ...marker, ...size, uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), @@ -110,7 +110,8 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro drawCount: ValueCell.create(vertices.length / 3), instanceCount: ValueCell.create(instanceCount), - dPointSizeAttenuation: ValueCell.create(true) + dPointSizeAttenuation: ValueCell.create(true), + dUseFog: ValueCell.create(defaults(props.useFog, true)), } const state: RenderableState = { depthMask: defaults(props.depthMask, true), @@ -163,10 +164,10 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro const indices = SortedArray.ofSingleton(elementId) return Element.Loci([{ unit, indices }]) } - return null + return EmptyLoci }, - applyFlags(loci: Loci, action: FlagAction) { - applyElementFlags(points.values.tFlag, currentGroup, loci, action) + mark(loci: Loci, action: MarkerAction) { + markElement(points.values.tMarker, currentGroup, loci, action) } } } diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 573c18547675ae1923077ddba3ede7d93e504a9f..a80d30964afe9cbc6ee714ccf02417eaa27e5a90 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -11,7 +11,7 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/r import { Unit, Element, Queries } from 'mol-model/structure'; import { UnitsRepresentation, DefaultStructureProps } from './index'; import { Task } from 'mol-task' -import { createTransforms, createColors, createSphereMesh, applyElementFlags } from './utils'; +import { createTransforms, createColors, createSphereMesh, markElement } from './utils'; import VertexMap from '../../shape/vertex-map'; import { deepEqual, defaults } from 'mol-util'; import { fillSerial } from 'mol-gl/renderable/util'; @@ -20,8 +20,8 @@ import { getMeshData } from '../../util/mesh-data'; import { Mesh } from '../../shape/mesh'; import { PickingId } from '../../util/picking'; import { SortedArray } from 'mol-data/int'; -import { createFlags, FlagAction } from '../../util/flag-data'; -import { Loci } from 'mol-model/loci'; +import { createMarkers, MarkerAction } from '../../util/marker-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) { let radius: Element.Property<number> @@ -75,15 +75,15 @@ export default function SpacefillUnitsRepresentation(): UnitsRepresentation<Spac await ctx.update('Computing spacefill colors'); const color = createColors(group, vertexMap, colorTheme) - await ctx.update('Computing spacefill flags'); - const flag = createFlags(instanceCount * elementCount) + await ctx.update('Computing spacefill marks'); + const marker = createMarkers(instanceCount * elementCount) const values: MeshValues = { ...getMeshData(mesh), aTransform: transforms, aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), ...color, - ...flag, + ...marker, uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), uInstanceCount: ValueCell.create(instanceCount), @@ -97,6 +97,7 @@ export default function SpacefillUnitsRepresentation(): UnitsRepresentation<Spac dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)), dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)), dFlipSided: ValueCell.create(defaults(props.flipSided, false)), + dUseFog: ValueCell.create(defaults(props.useFog, true)), } const state: RenderableState = { depthMask: defaults(props.depthMask, true), @@ -151,10 +152,10 @@ export default function SpacefillUnitsRepresentation(): UnitsRepresentation<Spac const indices = SortedArray.ofSingleton(elementId); return Element.Loci([{ unit, indices }]) } - return null + return EmptyLoci }, - applyFlags(loci: Loci, action: FlagAction) { - applyElementFlags(spheres.values.tFlag, currentGroup, loci, action) + mark(loci: Loci, action: MarkerAction) { + markElement(spheres.values.tMarker, currentGroup, loci, action) } } } diff --git a/src/mol-geo/representation/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts index e6719eaa741ce5030a06dc361d81c1ffebbdbcdf..8b876df1a60fabcbe855e87121eb005ad19adbe1 100644 --- a/src/mol-geo/representation/structure/utils.ts +++ b/src/mol-geo/representation/structure/utils.ts @@ -20,7 +20,7 @@ import { Task } from 'mol-task'; import { icosahedronVertexCount } from '../../primitive/icosahedron'; import { MeshBuilder } from '../../shape/mesh-builder'; import { TextureImage } from 'mol-gl/renderable/util'; -import { applyFlagAction, FlagAction } from '../../util/flag-data'; +import { applyMarkerAction, MarkerAction } from '../../util/marker-data'; import { Loci, isEveryLoci } from 'mol-model/loci'; export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) { @@ -89,13 +89,13 @@ export function createSphereMesh(unit: Unit, radius: Element.Property<number>, d } -export function applyElementFlags(tFlag: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: FlagAction) { +export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: MarkerAction) { let changed = false const elementCount = group.elements.length const instanceCount = group.units.length - const array = tFlag.ref.value.array + const array = tMarker.ref.value.array if (isEveryLoci(loci)) { - applyFlagAction(array, 0, elementCount * instanceCount, action) + applyMarkerAction(array, 0, elementCount * instanceCount, action) changed = true } else if (Element.isLoci(loci)) { for (const e of loci.elements) { @@ -103,7 +103,7 @@ export function applyElementFlags(tFlag: ValueCell<TextureImage>, group: Unit.Sy if (unitIdx !== -1) { for (let i = 0, il = e.indices.length; i < il; ++i) { const idx = unitIdx * elementCount + e.indices[i] - if (applyFlagAction(array, idx, idx + 1, action) && !changed) { + if (applyMarkerAction(array, idx, idx + 1, action) && !changed) { changed = true } } @@ -113,6 +113,6 @@ export function applyElementFlags(tFlag: ValueCell<TextureImage>, group: Unit.Sy return } if (changed) { - ValueCell.update(tFlag, tFlag.ref.value) + ValueCell.update(tMarker, tMarker.ref.value) } } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index cc702bfbd9f84a35830a1332ff173cfa8e375a2f..f0104fb993ac65d304b37348c47ad6bf112ce6a2 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -9,15 +9,15 @@ import { RenderObject } from 'mol-gl/render-object'; import { RepresentationProps, Representation } from '..'; import { VolumeData } from 'mol-model/volume'; import { PickingId } from '../../util/picking'; -import { Loci } from 'mol-model/loci'; -import { FlagAction } from '../../util/flag-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { MarkerAction } from '../../util/marker-data'; export interface VolumeElementRepresentation<P> { renderObjects: ReadonlyArray<RenderObject> create: (volumeData: VolumeData, props: P) => Task<void> update: (props: P) => Task<boolean> - getLoci: (pickingId: PickingId) => Loci | null - applyFlags: (loci: Loci, action: FlagAction) => void + getLoci: (pickingId: PickingId) => Loci + mark: (loci: Loci, action: MarkerAction) => void } export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { } @@ -39,9 +39,9 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat }, getLoci(pickingId: PickingId) { // TODO - return null + return EmptyLoci }, - applyFlags(loci: Loci, action: FlagAction) { + mark(loci: Loci, action: MarkerAction) { // TODO } } diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index 5bed7c8b5b05d250f6c3c99c1b8538a02e426305..c22140e978539e47eb184cad7d5576e944251a31 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/src/mol-geo/representation/volume/surface.ts @@ -18,8 +18,8 @@ import { createUniformColor } from '../../util/color-data'; import { getMeshData } from '../../util/mesh-data'; import { RenderableState, MeshValues } from 'mol-gl/renderable'; import { PickingId } from '../../util/picking'; -import { createEmptyFlags, FlagAction } from '../../util/flag-data'; -import { Loci } from 'mol-model/loci'; +import { createEmptyMarkers, MarkerAction } from '../../util/marker-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) { return Task.create<Mesh>('Volume Surface', async ctx => { @@ -45,7 +45,8 @@ export const DefaultSurfaceProps = { flatShaded: true, flipSided: true, doubleSided: true, - depthMask: true + depthMask: true, + useFog: true } export type SurfaceProps = Partial<typeof DefaultSurfaceProps> @@ -68,14 +69,14 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> { const instanceCount = 1 const color = createUniformColor({ value: 0x7ec0ee }) - const flag = createEmptyFlags() + const marker = createEmptyMarkers() const values: MeshValues = { ...getMeshData(mesh), aTransform: ValueCell.create(new Float32Array(Mat4.identity())), aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), ...color, - ...flag, + ...marker, uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), uInstanceCount: ValueCell.create(instanceCount), @@ -89,6 +90,7 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> { dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)), dFlatShaded: ValueCell.create(defaults(props.flatShaded, true)), dFlipSided: ValueCell.create(false), + dUseFog: ValueCell.create(defaults(props.useFog, true)), } const state: RenderableState = { depthMask: defaults(props.depthMask, true), @@ -107,9 +109,9 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> { }, getLoci(pickingId: PickingId) { // TODO - return null + return EmptyLoci }, - applyFlags(loci: Loci, action: FlagAction) { + mark(loci: Loci, action: MarkerAction) { // TODO } } diff --git a/src/mol-geo/util/flag-data.ts b/src/mol-geo/util/marker-data.ts similarity index 52% rename from src/mol-geo/util/flag-data.ts rename to src/mol-geo/util/marker-data.ts index e8049a53ef546b320919a804ed9b40df46a6b764..bda085fe98bdb90e10eb86b815f83d94062dc721 100644 --- a/src/mol-geo/util/flag-data.ts +++ b/src/mol-geo/util/marker-data.ts @@ -8,12 +8,12 @@ import { ValueCell } from 'mol-util/value-cell' import { Vec2 } from 'mol-math/linear-algebra' import { TextureImage, createTextureImage } from 'mol-gl/renderable/util'; -export type FlagData = { - tFlag: ValueCell<TextureImage> - uFlagTexSize: ValueCell<Vec2> +export type MarkerData = { + tMarker: ValueCell<TextureImage> + uMarkerTexSize: ValueCell<Vec2> } -export enum FlagAction { +export enum MarkerAction { Highlight, RemoveHighlight, Select, @@ -22,34 +22,34 @@ export enum FlagAction { Clear } -export function applyFlagAction(array: Uint8Array, start: number, end: number, action: FlagAction) { +export function applyMarkerAction(array: Uint8Array, start: number, end: number, action: MarkerAction) { let changed = false for (let i = start; i < end; ++i) { let v = array[i] switch (action) { - case FlagAction.Highlight: + case MarkerAction.Highlight: if (v % 2 === 0) { v += 1 changed = true } break - case FlagAction.RemoveHighlight: + case MarkerAction.RemoveHighlight: if (v % 2 !== 0) { v -= 1 changed = true } break - case FlagAction.Select: + case MarkerAction.Select: v += 2 changed = true break - case FlagAction.Deselect: + case MarkerAction.Deselect: if (v >= 2) { v -= 2 changed = true } break - case FlagAction.ToggleSelect: + case MarkerAction.ToggleSelect: if (v === 0) { v = 2 } else if (v === 1) { @@ -61,7 +61,7 @@ export function applyFlagAction(array: Uint8Array, start: number, end: number, a } changed = true break - case FlagAction.Clear: + case MarkerAction.Clear: v = 0 changed = true break @@ -71,32 +71,32 @@ export function applyFlagAction(array: Uint8Array, start: number, end: number, a return changed } -export function createFlags(count: number, flagData?: FlagData): FlagData { - const flags = flagData && flagData.tFlag.ref.value.array.length >= count - ? flagData.tFlag.ref.value +export function createMarkers(count: number, markerData?: MarkerData): MarkerData { + const markers = markerData && markerData.tMarker.ref.value.array.length >= count + ? markerData.tMarker.ref.value : createTextureImage(count, 1) - if (flagData) { - ValueCell.update(flagData.tFlag, flags) - ValueCell.update(flagData.uFlagTexSize, Vec2.create(flags.width, flags.height)) - return flagData + if (markerData) { + ValueCell.update(markerData.tMarker, markers) + ValueCell.update(markerData.uMarkerTexSize, Vec2.create(markers.width, markers.height)) + return markerData } else { return { - tFlag: ValueCell.create(flags), - uFlagTexSize: ValueCell.create(Vec2.create(flags.width, flags.height)), + tMarker: ValueCell.create(markers), + uMarkerTexSize: ValueCell.create(Vec2.create(markers.width, markers.height)), } } } -const emptyFlagTexture = { array: new Uint8Array(1), width: 1, height: 1 } -export function createEmptyFlags(flagData?: FlagData) { - if (flagData) { - ValueCell.update(flagData.tFlag, emptyFlagTexture) - ValueCell.update(flagData.uFlagTexSize, Vec2.create(1, 1)) - return flagData +const emptyMarkerTexture = { array: new Uint8Array(1), width: 1, height: 1 } +export function createEmptyMarkers(markerData?: MarkerData) { + if (markerData) { + ValueCell.update(markerData.tMarker, emptyMarkerTexture) + ValueCell.update(markerData.uMarkerTexSize, Vec2.create(1, 1)) + return markerData } else { return { - tFlag: ValueCell.create(emptyFlagTexture), - uFlagTexSize: ValueCell.create(Vec2.create(1, 1)), + tMarker: ValueCell.create(emptyMarkerTexture), + uMarkerTexSize: ValueCell.create(Vec2.create(1, 1)), } } } \ No newline at end of file diff --git a/src/mol-gl/_spec/renderable.spec.ts b/src/mol-gl/_spec/renderable.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9053ec9d130ac52dd20f2aad02565bc4fc6e029c --- /dev/null +++ b/src/mol-gl/_spec/renderable.spec.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { calculateBoundingSphere } from '../renderable/util'; + +describe('renderable', () => { + it('calculateBoundingSphere', () => { + const position = new Float32Array([ + 0, 0, 0, + 1, 0, 0 + ]) + const transform = new Float32Array([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 0, + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 1, 0, 0, 0, + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 2, 0, 0, 0 + ]) + + const bs = calculateBoundingSphere( + position, position.length / 3, + transform, transform.length / 16 + ) + + expect(bs.radius).toBe(1.5) + expect(bs.center).toEqual([1.5, 0.0, 0.0]) + }) +}) diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 0d087d672c95cb91dffe70c523355b8e98db2c28..c922e321966fa208a7cc2f9be2816290731083ce 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -19,7 +19,7 @@ import { RenderableState } from '../renderable'; import { createPointRenderObject } from '../render-object'; import { PointValues } from '../renderable/point'; import Scene from '../scene'; -import { createEmptyFlags } from 'mol-geo/util/flag-data'; +import { createEmptyMarkers } from 'mol-geo/util/marker-data'; // function writeImage(gl: WebGLRenderingContext, width: number, height: number) { // const pixels = new Uint8Array(width * height * 4) @@ -49,7 +49,7 @@ function createPoints() { const aInstanceId = ValueCell.create(fillSerial(new Float32Array(1))) const color = createUniformColor({ value: 0xFF0000 }) const size = createUniformSize({ value: 1 }) - const flag = createEmptyFlags() + const marker = createEmptyMarkers() const aTransform = ValueCell.create(new Float32Array(16)) const m4 = Mat4.identity() @@ -61,7 +61,7 @@ function createPoints() { aTransform, aInstanceId, ...color, - ...flag, + ...marker, ...size, uAlpha: ValueCell.create(1.0), @@ -71,7 +71,8 @@ function createPoints() { drawCount: ValueCell.create(3), instanceCount: ValueCell.create(1), - dPointSizeAttenuation: ValueCell.create(true) + dPointSizeAttenuation: ValueCell.create(true), + dUseFog: ValueCell.create(true), } const state: RenderableState = { visible: true, @@ -96,7 +97,7 @@ describe('renderer', () => { expect(ctx.programCache.count).toBe(0); expect(ctx.shaderCache.count).toBe(0); - renderer.setViewport({ x: 0, y: 0, width: 64, height: 48 }) + renderer.setViewport(0, 0, 64, 48) expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[2]).toBe(64) expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[3]).toBe(48) }) diff --git a/src/mol-gl/object3d.ts b/src/mol-gl/object3d.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8e4a93ec8e8c8c2fbceb51723391ef0a48a079d --- /dev/null +++ b/src/mol-gl/object3d.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Vec3, Mat4 } from 'mol-math/linear-algebra'; + +export interface Object3D { + readonly view: Mat4, + readonly position: Vec3, + readonly direction: Vec3, + readonly up: Vec3, + + update: () => void +} + +export function createObject3D(): Object3D { + const view = Mat4.identity() + const position = Vec3.create(0, 0, 0) + const direction = Vec3.create(0, 0, -1) + const up = Vec3.create(0, 1, 0) + + const center = Vec3.zero() + + return { + view, + position, + direction, + up, + + update() { + // console.log('position', position) + // console.log('direction', direction) + // console.log('up', up) + Vec3.add(center, position, direction) + Mat4.lookAt(view, position, center, up) + // Mat4.lookAt(view, center, position, up) + // console.log('view', view) + } + } +} \ No newline at end of file diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index b3a72be16655e619f1c32a9dda7f9c9400fe0b8f..9d458fe4cbda39892e2555992bdfc213dc79a2f3 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -5,17 +5,20 @@ */ import { Program } from './webgl/program'; -import { RenderableValues, Values, RenderableSchema } from './renderable/schema'; +import { RenderableValues, Values, RenderableSchema, BaseValues } from './renderable/schema'; import { RenderVariant, RenderItem } from './webgl/render-item'; +import { Sphere3D } from 'mol-math/geometry'; +import { calculateBoundingSphereFromValues } from './renderable/util'; export type RenderableState = { visible: boolean depthMask: boolean } -export interface Renderable<T extends RenderableValues> { +export interface Renderable<T extends RenderableValues & BaseValues> { readonly values: T readonly state: RenderableState + readonly boundingSphere: Sphere3D render: (variant: RenderVariant) => void getProgram: (variant: RenderVariant) => Program @@ -23,14 +26,24 @@ export interface Renderable<T extends RenderableValues> { dispose: () => void } -export function createRenderable<T extends Values<RenderableSchema>>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> { +export function createRenderable<T extends Values<RenderableSchema> & BaseValues>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> { + let boundingSphere: Sphere3D | undefined + return { get values () { return values }, get state () { return state }, + get boundingSphere () { + if (boundingSphere) return boundingSphere + boundingSphere = calculateBoundingSphereFromValues(values) + return boundingSphere + }, render: (variant: RenderVariant) => renderItem.render(variant), getProgram: (variant: RenderVariant) => renderItem.getProgram(variant), - update: () => renderItem.update(), + update: () => { + renderItem.update() + boundingSphere = undefined + }, dispose: () => renderItem.destroy() } } diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index de475c520699e703f1b8c8a729030fd7426639a5..adfa5985b56104dc556ab8938b7d672fdc9a8ab7 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -126,6 +126,13 @@ export const GlobalUniformSchema = { uPixelRatio: UniformSpec('f'), uViewportHeight: UniformSpec('f'), + + uHighlightColor: UniformSpec('v3'), + uSelectColor: UniformSpec('v3'), + + uFogNear: UniformSpec('f'), + uFogFar: UniformSpec('f'), + uFogColor: UniformSpec('v3'), } export type GlobalUniformSchema = typeof GlobalUniformSchema export type GlobalUniformValues = { [k in keyof GlobalUniformSchema]: ValueCell<any> } @@ -146,15 +153,16 @@ export const BaseSchema = { uElementCount: UniformSpec('i'), uColor: UniformSpec('v3'), uColorTexSize: UniformSpec('v2'), - uFlagTexSize: UniformSpec('v2'), + uMarkerTexSize: UniformSpec('v2'), tColor: TextureSpec('rgb', 'ubyte'), - tFlag: TextureSpec('alpha', 'ubyte'), + tMarker: TextureSpec('alpha', 'ubyte'), drawCount: ValueSpec('number'), instanceCount: ValueSpec('number'), dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'element', 'element_instance']), + dUseFog: DefineSpec('boolean'), } export type BaseSchema = typeof BaseSchema export type BaseValues = Values<BaseSchema> \ No newline at end of file diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index 3c6048dd84a8a12f00fb05659bd7362dc061aa03..86f791611b5e64057ab7a1aee6a4714ef4476471 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -4,6 +4,10 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { Sphere3D } from 'mol-math/geometry' +import { Mat4, Vec3 } from 'mol-math/linear-algebra' +import { ValueCell } from 'mol-util'; + export function calculateTextureInfo (n: number, itemSize: number) { const sqN = Math.sqrt(n * itemSize) let width = Math.ceil(sqN) @@ -27,4 +31,78 @@ export function fillSerial<T extends Helpers.NumberArray> (array: T) { const n = array.length for (let i = 0; i < n; ++i) array[ i ] = i return array +} + +export interface PositionValues { + aPosition: ValueCell<Float32Array> + drawCount: ValueCell<number>, + aTransform: ValueCell<Float32Array>, + instanceCount: ValueCell<number>, +} + +function getPositionDataFromValues(values: PositionValues) { + return { + position: values.aPosition.ref.value, + positionCount: values.drawCount.ref.value / 3 / 3, + transform: values.aTransform.ref.value, + transformCount: values.instanceCount.ref.value + } +} + +export function calculateBoundingSphereFromValues(values: PositionValues){ + const { position, positionCount, transform, transformCount } = getPositionDataFromValues(values) + return calculateBoundingSphere(position, positionCount, transform, transformCount) +} + +export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number): Sphere3D { + + const m = Mat4.zero() + + let cx = 0, cy = 0, cz = 0; + let radiusSq = 0; + + for (let i = 0, _i = positionCount * 3; i < _i; i += 3) { + cx += position[i]; + cy += position[i + 1]; + cz += position[i + 2]; + } + + if (positionCount > 0) { + cx /= positionCount; + cy /= positionCount; + cz /= positionCount; + } + + for (let i = 0, _i = positionCount * 3; i < _i; i += 3) { + const dx = position[i] - cx + const dy = position[i + 1] - cy + const dz = position[i + 2] - cz; + const d = dx * dx + dy * dy + dz * dz; + if (d > radiusSq) radiusSq = d; + } + + const c = Vec3.create(cx, cy, cz) + const ct = Vec3.zero() + + const center = Vec3.zero() + const centers = new Float32Array(3 * transformCount) + + for (let i = 0, _i = transformCount; i < _i; ++i) { + Mat4.fromArray(m, transform, i * 16) + Vec3.transformMat4(ct, c, m) + Vec3.add(center, center, ct) + Vec3.toArray(ct, centers, i * 3) + } + + Vec3.scale(center, center, 1 / transformCount) + + let r = Math.sqrt(radiusSq) + let radius = r + + for (let i = 0, _i = transformCount; i < _i; ++i) { + Vec3.fromArray(ct, centers, i * 3) + radius = Math.max(radius, Vec3.distance(center, ct) + r) + } + + return { center, radius }; } \ No newline at end of file diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index ec25ac32826ca0c2fb8623ced38b3c18f1b67a91..22e64a84405ff4e48940befa7e46dcd04113dd8b 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -14,7 +14,7 @@ import { Mat4, Vec3 } from 'mol-math/linear-algebra'; import { Renderable } from './renderable'; import { Color } from 'mol-util/color'; import { ValueCell } from 'mol-util'; -import { RenderableValues, GlobalUniformValues } from './renderable/schema'; +import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema'; import { RenderVariant } from './webgl/render-item'; export interface RendererStats { @@ -29,13 +29,12 @@ export interface RendererStats { } interface Renderer { - render: (scene: Scene, variant: RenderVariant) => void + readonly stats: RendererStats - setViewport: (viewport: Viewport) => void + render: (scene: Scene, variant: RenderVariant) => void + setViewport: (x: number, y: number, width: number, height: number) => void setClearColor: (color: Color) => void getImageData: () => ImageData - - stats: RendererStats dispose: () => void } @@ -50,12 +49,14 @@ namespace Renderer { const { gl } = ctx let { clearColor, viewport: _viewport } = { ...DefaultRendererProps, ...props } - const model = Mat4.identity() const viewport = Viewport.clone(_viewport) // const lightPosition = Vec3.create(0, 0, -100) const lightColor = Vec3.create(1.0, 1.0, 1.0) const lightAmbient = Vec3.create(0.5, 0.5, 0.5) + const highlightColor = Vec3.create(1.0, 0.4, 0.6) + const selectColor = Vec3.create(0.2, 1.0, 0.1) + const fogColor = Vec3.create(0.0, 0.0, 0.0) function setClearColor(color: Color) { const [ r, g, b ] = Color.toRgbNormalized(color) @@ -64,7 +65,7 @@ namespace Renderer { setClearColor(clearColor) const globalUniforms: GlobalUniformValues = { - uModel: ValueCell.create(Mat4.clone(model)), + uModel: ValueCell.create(Mat4.identity()), uView: ValueCell.create(Mat4.clone(camera.view)), uProjection: ValueCell.create(Mat4.clone(camera.projection)), @@ -72,11 +73,18 @@ namespace Renderer { uViewportHeight: ValueCell.create(viewport.height), uLightColor: ValueCell.create(Vec3.clone(lightColor)), - uLightAmbient: ValueCell.create(Vec3.clone(lightAmbient)) + uLightAmbient: ValueCell.create(Vec3.clone(lightAmbient)), + + uHighlightColor: ValueCell.create(Vec3.clone(highlightColor)), + uSelectColor: ValueCell.create(Vec3.clone(selectColor)), + + uFogNear: ValueCell.create(camera.near), + uFogFar: ValueCell.create(camera.far / 50), + uFogColor: ValueCell.create(Vec3.clone(fogColor)), } let currentProgramId = -1 - const renderObject = (r: Renderable<RenderableValues>, variant: RenderVariant) => { + const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: RenderVariant) => { const program = r.getProgram(variant) if (r.state.visible) { if (currentProgramId !== program.id) { @@ -105,9 +113,13 @@ namespace Renderer { } const render = (scene: Scene, variant: RenderVariant) => { + ValueCell.update(globalUniforms.uModel, scene.view) ValueCell.update(globalUniforms.uView, camera.view) ValueCell.update(globalUniforms.uProjection, camera.projection) + ValueCell.update(globalUniforms.uFogFar, camera.fogFar) + ValueCell.update(globalUniforms.uFogNear, camera.fogNear) + currentProgramId = -1 gl.depthMask(true) @@ -128,10 +140,10 @@ namespace Renderer { render, setClearColor, - setViewport: (newViewport: Viewport) => { - Viewport.copy(viewport, newViewport) - gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height) - ValueCell.update(globalUniforms.uViewportHeight, viewport.height) + setViewport: (x: number, y: number, width: number, height: number) => { + Viewport.set(viewport, x, y, width, height) + gl.viewport(x, y, width, height) + ValueCell.update(globalUniforms.uViewportHeight, height) }, getImageData: () => { const { width, height } = viewport diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 52f09894498d79d79f49858495b71b122bf8c69c..57764dd718ca6ca31a704306ea8fd8ad5dbda4a0 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -6,29 +6,68 @@ import { Renderable } from './renderable' import { Context } from './webgl/context'; -import { RenderableValues } from './renderable/schema'; +import { RenderableValues, BaseValues } from './renderable/schema'; import { RenderObject, createRenderable } from './render-object'; +import { Object3D, createObject3D } from './object3d'; +import { Sphere3D } from 'mol-math/geometry'; +import { Vec3 } from 'mol-math/linear-algebra'; +function calculateBoundingSphere(renderableMap: Map<RenderObject, Renderable<RenderableValues & BaseValues>>): Sphere3D { + let count = 0 + const center = Vec3.zero() + renderableMap.forEach((r, o) => { + if (r.boundingSphere.radius) { + Vec3.add(center, center, r.boundingSphere.center) + ++count + } + }) + if (count > 0) { + Vec3.scale(center, center, 1 / count) + } + + let radius = 0 + renderableMap.forEach((r, o) => { + if (r.boundingSphere.radius) { + radius = Math.max(radius, Vec3.distance(center, r.boundingSphere.center) + r.boundingSphere.radius) + } + }) + + return { center, radius }; +} + +interface Scene extends Object3D { + readonly count: number + readonly boundingSphere: Sphere3D -interface Scene { add: (o: RenderObject) => void remove: (o: RenderObject) => void - update: () => void clear: () => void forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void eachTransparent: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void - count: number } namespace Scene { export function create(ctx: Context): Scene { - const renderableMap = new Map<RenderObject, Renderable<RenderableValues>>() + const renderableMap = new Map<RenderObject, Renderable<RenderableValues & BaseValues>>() + let boundingSphere: Sphere3D | undefined + + const { view, position, up, direction, update } = createObject3D() return { + // ...createObject3D(), // TODO does not work in conjunction with getter + view, position, up, direction, + + update: () => { + update() + renderableMap.forEach((o, r) => o.update()) + boundingSphere = undefined + }, + add: (o: RenderObject) => { if (!renderableMap.has(o)) { renderableMap.set(o, createRenderable(ctx, o)) + boundingSphere = undefined } else { console.warn(`RenderObject with id '${o.id}' already present`) } @@ -38,14 +77,13 @@ namespace Scene { if (renderable) { renderable.dispose() renderableMap.delete(o) + boundingSphere = undefined } }, - update: () => { - renderableMap.forEach((r, o) => r.update()) - }, clear: () => { renderableMap.forEach(renderable => renderable.dispose()) renderableMap.clear() + boundingSphere = undefined }, forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => { renderableMap.forEach(callbackFn) @@ -62,6 +100,12 @@ namespace Scene { }, get count() { return renderableMap.size + }, + get boundingSphere() { + if (boundingSphere) return boundingSphere + // TODO avoid array creation + boundingSphere = calculateBoundingSphere(renderableMap) + return boundingSphere } } } diff --git a/src/mol-gl/shader/chunks/apply-fog.glsl b/src/mol-gl/shader/chunks/apply-fog.glsl new file mode 100644 index 0000000000000000000000000000000000000000..9e176cbaa6c52918811d3493e139614abad5e4a0 --- /dev/null +++ b/src/mol-gl/shader/chunks/apply-fog.glsl @@ -0,0 +1,6 @@ +// #ifdef dUseFog + float depth = length(vViewPosition); + // float depth = gl_FragCoord.z / gl_FragCoord.w; + float fogFactor = smoothstep(uFogNear, uFogFar, depth); + gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor); +// #endif \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..c457e1b7585ad44b1e8d7ac2c5baa13854b52bc9 --- /dev/null +++ b/src/mol-gl/shader/chunks/apply-marker-color.glsl @@ -0,0 +1,8 @@ +float marker = vMarker * 255.0; +if (marker > 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); + } +} \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-assign-varying.glsl b/src/mol-gl/shader/chunks/assign-color-varying.glsl similarity index 100% rename from src/mol-gl/shader/chunks/color-assign-varying.glsl rename to src/mol-gl/shader/chunks/assign-color-varying.glsl diff --git a/src/mol-gl/shader/chunks/assign-marker-varying.glsl b/src/mol-gl/shader/chunks/assign-marker-varying.glsl new file mode 100644 index 0000000000000000000000000000000000000000..1afbdd7cfbeca72ebeb83a4bcaef4c91063874cd --- /dev/null +++ b/src/mol-gl/shader/chunks/assign-marker-varying.glsl @@ -0,0 +1 @@ +vMarker = readFromTexture(tMarker, aInstanceId * float(uElementCount) + aElementId, uMarkerTexSize).a; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-assign-material.glsl b/src/mol-gl/shader/chunks/assign-material-color.glsl similarity index 100% rename from src/mol-gl/shader/chunks/color-assign-material.glsl rename to src/mol-gl/shader/chunks/assign-material-color.glsl diff --git a/src/mol-gl/shader/chunks/assign-position.glsl b/src/mol-gl/shader/chunks/assign-position.glsl new file mode 100644 index 0000000000000000000000000000000000000000..06231cf7ff806afbd988c00961471fe9cb3b81dd --- /dev/null +++ b/src/mol-gl/shader/chunks/assign-position.glsl @@ -0,0 +1,4 @@ +mat4 modelView = uView * uModel * aTransform; +vec4 mvPosition = modelView * vec4(aPosition, 1.0); +vViewPosition = mvPosition.xyz; +gl_Position = uProjection * mvPosition; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl b/src/mol-gl/shader/chunks/common-frag-params.glsl index 744b3a1a247019dc92cc745374f38c138ba387d2..7470f1041311568d7010e15b2f73c67d09651585 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl @@ -2,4 +2,10 @@ uniform int uObjectId; uniform int uInstanceCount; uniform int uElementCount; -varying float vFlag; \ No newline at end of file +uniform vec3 uHighlightColor; +uniform vec3 uSelectColor; +varying float vMarker; + +uniform float uFogNear; +uniform float uFogFar; +uniform vec3 uFogColor; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl b/src/mol-gl/shader/chunks/common-vert-params.glsl index 005d347d55fe9d79fa16f53c46681b4f44d50ef9..27d1dd0b338b166efc5479233cd2670fc489f62c 100644 --- a/src/mol-gl/shader/chunks/common-vert-params.glsl +++ b/src/mol-gl/shader/chunks/common-vert-params.glsl @@ -4,7 +4,7 @@ uniform int uObjectId; uniform int uInstanceCount; uniform int uElementCount; -uniform vec2 uFlagTexSize; -uniform sampler2D tFlag; -varying float vFlag; +uniform vec2 uMarkerTexSize; +uniform sampler2D tMarker; +varying float vMarker; #pragma glslify: readFromTexture = require(../utils/read-from-texture.glsl) \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index 6350ea200a8ba7b950f44554b53a54a2bb03f702..cc387cb22b293464889921d6f48c91bdd9b1a7f2 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/src/mol-gl/shader/mesh.frag @@ -36,7 +36,7 @@ const float albedo = 0.95; void main() { // material color - #pragma glslify: import('./chunks/color-assign-material.glsl') + #pragma glslify: import('./chunks/assign-material-color.glsl') #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking) // gl_FragColor = vec4(material.r, material.g, material.a, 1.0); @@ -76,20 +76,7 @@ void main() { gl_FragColor.rgb = finalColor; gl_FragColor.a = uAlpha; - // if (vFlag == 1.0) { - // gl_FragColor.rgb = mix(vec3(1.0, 0.4, 0.6), gl_FragColor.rgb, 0.3); - // } - - float flag = floor(vFlag * 255.0); - - if (flag == 0.0) { - // diffuseColor = vec4( vColor, opacity ); - } else if (mod(flag, 2.0) == 0.0) { - // diffuseColor = vec4(highlightColor, opacity); - gl_FragColor.rgb = mix(vec3(1.0, 0.4, 0.6), gl_FragColor.rgb, 0.3); - } else { - // diffuseColor = vec4(selectionColor, opacity); - gl_FragColor.rgb = mix(vec3(0.2, 1.0, 0.1), gl_FragColor.rgb, 0.3); - } + #pragma glslify: import('./chunks/apply-marker-color.glsl') + #pragma glslify: import('./chunks/apply-fog.glsl') #endif } \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert index 9b9ec9ea3ed9efe91980b50ef2fe8d3db6feb13d..0f7b837d2cd7569c1997ff057ca32cc4fc2f4e5a 100644 --- a/src/mol-gl/shader/mesh.vert +++ b/src/mol-gl/shader/mesh.vert @@ -26,13 +26,9 @@ varying vec3 vViewPosition; #pragma glslify: transpose = require(./utils/transpose.glsl) void main(){ - #pragma glslify: import('./chunks/color-assign-varying.glsl') - vFlag = readFromTexture(tFlag, aInstanceId * float(uElementCount) + aElementId, uFlagTexSize).a; - - mat4 modelView = uView * uModel * aTransform; - vec4 mvPosition = modelView * vec4(aPosition, 1.0); - vViewPosition = mvPosition.xyz; - gl_Position = uProjection * mvPosition; + #pragma glslify: import('./chunks/assign-color-varying.glsl') + #pragma glslify: import('./chunks/assign-marker-varying.glsl') + #pragma glslify: import('./chunks/assign-position.glsl') #ifndef dFlatShaded mat3 normalMatrix = transpose(inverse(mat3(modelView))); diff --git a/src/mol-gl/shader/point.frag b/src/mol-gl/shader/point.frag index 9e7e343b1cf80d35cbbb82d69ce9d481d8985cc4..ae18b4a35f8ac2bf100ba5b52873c1bb5ab4d1e5 100644 --- a/src/mol-gl/shader/point.frag +++ b/src/mol-gl/shader/point.frag @@ -13,6 +13,10 @@ precision highp int; uniform float uAlpha; void main(){ - #pragma glslify: import('./chunks/color-assign-material.glsl') + #pragma glslify: import('./chunks/assign-material-color.glsl') + gl_FragColor = vec4(material, uAlpha); + + #pragma glslify: import('./chunks/apply-marker-color.glsl') + #pragma glslify: import('./chunks/apply-fog.glsl') } \ No newline at end of file diff --git a/src/mol-gl/shader/point.vert b/src/mol-gl/shader/point.vert index 44c08c8cf9bc6d8e9da55d04e573c5da52430245..8e2c29ef11cc3192c00496a06bcd8d6558fe297e 100644 --- a/src/mol-gl/shader/point.vert +++ b/src/mol-gl/shader/point.vert @@ -25,10 +25,8 @@ attribute float aInstanceId; attribute float aElementId; void main(){ - #pragma glslify: import('./chunks/color-assign-varying.glsl') - - mat4 modelView = uView * uModel * aTransform; - vec4 mvPosition = modelView * vec4(aPosition, 1.0); + #pragma glslify: import('./chunks/assign-color-varying.glsl') + #pragma glslify: import('./chunks/assign-position.glsl') #if defined(dSizeType_uniform) float size = uSize; diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts index 586ee66d308c5835340cb233bcfa78125f32487b..85b9fa4deae88a448ffcd192b2d2b52faad27a55 100644 --- a/src/mol-model/loci.ts +++ b/src/mol-model/loci.ts @@ -14,4 +14,11 @@ export function isEveryLoci(x: any): x is EveryLoci { return !!x && x.kind === 'every-loci'; } -export type Loci = Element.Loci | Link.Loci | EveryLoci \ No newline at end of file +/** A Loci that that is empty */ +export const EmptyLoci = { kind: 'empty-loci' as 'empty-loci' } +export type EmptyLoci = typeof EmptyLoci +export function isEmptyLoci(x: any): x is EmptyLoci { + return !!x && x.kind === 'empty-loci'; +} + +export type Loci = Element.Loci | Link.Loci | EveryLoci | EmptyLoci \ No newline at end of file diff --git a/src/mol-util/input/input-observer.ts b/src/mol-util/input/input-observer.ts index 5d572d23f3a6b75b4630ed477973769b4749fcd3..b1f7073cf5858f442cca6e071160faeea6ec8717 100644 --- a/src/mol-util/input/input-observer.ts +++ b/src/mol-util/input/input-observer.ts @@ -98,6 +98,7 @@ export type MoveInput = { y: number, pageX: number, pageY: number, + inside: boolean, } & BaseInput export type PinchInput = { @@ -355,7 +356,8 @@ namespace InputObserver { eventOffset(pointerEnd, ev) const { pageX, pageY } = ev const [ x, y ] = pointerEnd - move.next({ x, y, pageX, pageY, buttons, modifiers }) + const inside = insideBounds(pointerEnd) + move.next({ x, y, pageX, pageY, buttons, modifiers, inside }) if (dragging === DraggingState.Stopped) return diff --git a/src/mol-view/camera/base.ts b/src/mol-view/camera/base.ts index 3b9b78fec861384a01e59b086488accd35af7317..891a8ec69ec11a0120dae82cb5e2d0fe250bc953 100644 --- a/src/mol-view/camera/base.ts +++ b/src/mol-view/camera/base.ts @@ -6,22 +6,22 @@ import { Mat4, Vec3, Vec4 } from 'mol-math/linear-algebra' import { cameraProject, cameraUnproject, cameraLookAt, Viewport } from './util'; +import { Object3D, createObject3D } from 'mol-gl/object3d'; -export interface Camera { - view: Mat4, - projection: Mat4, - projectionView: Mat4, - inverseProjectionView: Mat4, +export interface Camera extends Object3D { + readonly projection: Mat4, + readonly projectionView: Mat4, + readonly inverseProjectionView: Mat4, + readonly viewport: Viewport, - viewport: Viewport, - position: Vec3, - direction: Vec3, - up: Vec3, + near: number, + far: number, + fogNear: number, + fogFar: number, translate: (v: Vec3) => void, reset: () => void, lookAt: (target: Vec3) => void, - update: () => void, project: (out: Vec4, point: Vec3) => Vec4, unproject: (out: Vec3, point: Vec3) => Vec3 } @@ -30,7 +30,11 @@ export const DefaultCameraProps = { position: Vec3.zero(), direction: Vec3.create(0, 0, -1), up: Vec3.create(0, 1, 0), - viewport: Viewport.create(-1, -1, 1, 1) + viewport: Viewport.create(-1, -1, 1, 1), + near: 0.1, + far: 10000, + fogNear: 0.1, + fogFar: 10000, } export type CameraProps = Partial<typeof DefaultCameraProps> @@ -38,11 +42,12 @@ export namespace Camera { export function create(props?: CameraProps): Camera { const p = { ...DefaultCameraProps, ...props }; + const { view, position, direction, up } = createObject3D() + Vec3.copy(position, p.position) + Vec3.copy(direction, p.direction) + Vec3.copy(up, p.up) + const projection = Mat4.identity() - const view = Mat4.identity() - const position = Vec3.clone(p.position) - const direction = Vec3.clone(p.direction) - const up = Vec3.clone(p.up) const viewport = Viewport.clone(p.viewport) const projectionView = Mat4.identity() const inverseProjectionView = Mat4.identity() @@ -89,6 +94,15 @@ export namespace Camera { direction, up, + get near() { return p.near }, + set near(value: number) { p.near = value }, + get far() { return p.far }, + set far(value: number) { p.far = value }, + get fogNear() { return p.fogNear }, + set fogNear(value: number) { p.fogNear = value }, + get fogFar() { return p.fogFar }, + set fogFar(value: number) { p.fogFar = value }, + translate, reset, lookAt, diff --git a/src/mol-view/camera/orthographic.ts b/src/mol-view/camera/orthographic.ts index e94778a5049a795a95301187acbd685450e40586..0ffdd02fcbce683e436c0030ffe0517135c6ceda 100644 --- a/src/mol-view/camera/orthographic.ts +++ b/src/mol-view/camera/orthographic.ts @@ -1,5 +1 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ \ No newline at end of file +// TODO \ No newline at end of file diff --git a/src/mol-view/camera/perspective.ts b/src/mol-view/camera/perspective.ts index 88745f75464e94e2efa1b11fb4f5a65fc2642d2b..1117fa3b1f6e931fc09a12c79bb3b0bd68b64da9 100644 --- a/src/mol-view/camera/perspective.ts +++ b/src/mol-view/camera/perspective.ts @@ -15,8 +15,6 @@ export interface PerspectiveCamera extends Camera { export const DefaultPerspectiveCameraProps = { fov: Math.PI / 4, - near: 0.1, - far: 10000, ...DefaultCameraProps } export type PerspectiveCameraProps = Partial<typeof DefaultPerspectiveCameraProps> @@ -48,12 +46,6 @@ export namespace PerspectiveCamera { ...camera, update, - get far() { return far }, - set far(value: number) { far = value }, - - get near() { return near }, - set near(value: number) { near = value }, - get fov() { return fov }, set fov(value: number) { fov = value }, } diff --git a/src/mol-view/camera/util.ts b/src/mol-view/camera/util.ts index a657c6fefa43f255835d8e8d37d9cad14ea42db6..11c77e70db107fb6c41e74befed0a58aa1231348 100644 --- a/src/mol-view/camera/util.ts +++ b/src/mol-view/camera/util.ts @@ -20,10 +20,16 @@ export namespace Viewport { export function clone(viewport: Viewport): Viewport { return { ...viewport } } - export function copy(target: Viewport, source: Viewport): Viewport { return Object.assign(target, source) } + export function set(viewport: Viewport, x: number, y: number, width: number, height: number): Viewport { + viewport.x = x + viewport.y = y + viewport.width = width + viewport.height = height + return viewport + } } const tmpVec3 = Vec3.zero() diff --git a/src/mol-view/controls/trackball.ts b/src/mol-view/controls/trackball.ts index 4f05c84465127e974a1c3e5ba2a2dffb23291423..554d81f0af8d3f7c18ab37cb0bbb5c357216609f 100644 --- a/src/mol-view/controls/trackball.ts +++ b/src/mol-view/controls/trackball.ts @@ -12,6 +12,7 @@ import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra'; import { cameraLookAt, Viewport } from '../camera/util'; import InputObserver, { DragInput, WheelInput, ButtonsFlag, PinchInput } from 'mol-util/input/input-observer'; +import { Object3D } from 'mol-gl/object3d'; export const DefaultTrackballControlsProps = { noScroll: true, @@ -29,12 +30,6 @@ export const DefaultTrackballControlsProps = { } export type TrackballControlsProps = Partial<typeof DefaultTrackballControlsProps> -interface Object { - position: Vec3, - direction: Vec3, - up: Vec3, -} - interface TrackballControls { viewport: Viewport target: Vec3 @@ -50,7 +45,7 @@ interface TrackballControls { } namespace TrackballControls { - export function create (input: InputObserver, object: Object, props: TrackballControlsProps = {}): TrackballControls { + export function create (input: InputObserver, object: Object3D, props: TrackballControlsProps = {}): TrackballControls { const p = { ...DefaultTrackballControlsProps, ...props } const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 } diff --git a/src/mol-view/label.ts b/src/mol-view/label.ts index 0ea82df1ba0a07c339d76453580f942cf19a85cd..ddb518bd654858148307eaa74e8e92da40ff43c4 100644 --- a/src/mol-view/label.ts +++ b/src/mol-view/label.ts @@ -6,7 +6,6 @@ */ import { Unit, Element, Queries } from 'mol-model/structure'; -import { Link } from 'mol-model/structure/structure/unit/links'; import { Loci } from 'mol-model/loci'; const elementLocA = Element.Location() @@ -17,21 +16,29 @@ function setElementLocation(loc: Element.Location, unit: Unit, index: number) { loc.element = unit.elements[index] } -export function labelFirst(loci: Loci) { - if(Element.isLoci(loci)) { - const e = loci.elements[0] - if (e && e.indices[0] !== undefined) { - return elementLabel(Element.Location(e.unit, e.indices[0])) - } - } else if (Link.isLoci(loci)) { - const bond = loci.links[0] - if (bond) { - setElementLocation(elementLocA, bond.aUnit, bond.aIndex) - setElementLocation(elementLocB, bond.bUnit, bond.bIndex) - return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}` - } +export function labelFirst(loci: Loci): string { + switch (loci.kind) { + case 'element-loci': + const e = loci.elements[0] + if (e && e.indices[0] !== undefined) { + return elementLabel(Element.Location(e.unit, e.indices[0])) + } else { + return 'Nothing' + } + case 'link-loci': + const bond = loci.links[0] + if (bond) { + setElementLocation(elementLocA, bond.aUnit, bond.aIndex) + setElementLocation(elementLocB, bond.bUnit, bond.bIndex) + return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}` + } else { + return 'Nothing' + } + case 'every-loci': + return 'Evertything' + case 'empty-loci': + return 'Nothing' } - return '' } export function elementLabel(loc: Element.Location) { diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 06671bcfe105e537b5a6f21692b227879ca1c13b..32684d7f413c0fd6ad575311d7ba2f9aff50e733 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -22,9 +22,8 @@ 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/util/picking'; -import { labelFirst } from './label'; -import { FlagAction } from 'mol-geo/util/flag-data'; -import { EveryLoci } from 'mol-model/loci'; +import { MarkerAction } from 'mol-geo/util/marker-data'; +import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; interface Viewer { center: (p: Vec3) => void @@ -41,7 +40,9 @@ interface Viewer { requestDraw: () => void animate: () => void pick: () => void - identify: (x: number, y: number) => void + identify: (x: number, y: number) => PickingId + mark: (loci: Loci, action: MarkerAction) => void + getLoci: (pickingId: PickingId) => Loci reprCount: BehaviorSubject<number> identified: BehaviorSubject<string> @@ -76,45 +77,14 @@ namespace Viewer { const startTime = performance.now() const didDraw = new BehaviorSubject(0) - const input = InputObserver.create(canvas) - input.resize.subscribe(handleResize) - input.move.subscribe(({x, y}) => { - const p = identify(x, y) - let label = '' - reprMap.forEach((roSet, repr) => { - repr.applyFlags(EveryLoci, FlagAction.RemoveHighlight) - const loci = repr.getLoci(p) - if (loci) { - label = labelFirst(loci) - repr.applyFlags(loci, FlagAction.Highlight) - } - scene.update() - requestDraw() - }) - identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`) - }) - input.click.subscribe(({x, y}) => { - const p = identify(x, y) - reprMap.forEach((roSet, repr) => { - const loci = repr.getLoci(p) - if (loci) { - repr.applyFlags(loci, FlagAction.ToggleSelect) - scene.update() - requestDraw() - } - }) - }) const camera = PerspectiveCamera.create({ near: 0.1, far: 10000, position: Vec3.create(0, 0, 50) }) - - const controls = TrackballControls.create(input, camera, { - - }) + // camera.lookAt(Vec3.create(0, 0, 0)) const gl = getWebGLContext(canvas, { alpha: false, @@ -128,6 +98,8 @@ namespace Viewer { const ctx = createContext(gl) const scene = Scene.create(ctx) + // const controls = TrackballControls.create(input, scene, {}) + const controls = TrackballControls.create(input, camera, {}) const renderer = Renderer.create(ctx, camera) const pickScale = 1 / 4 @@ -140,13 +112,71 @@ namespace Viewer { let pickDirty = true let drawPending = false const prevProjectionView = Mat4.zero() + const prevSceneView = Mat4.zero() + + function getLoci(pickingId: PickingId) { + let loci: Loci = EmptyLoci + reprMap.forEach((_, repr) => { + const _loci = repr.getLoci(pickingId) + if (!isEmptyLoci(_loci)) { + if (!isEmptyLoci(loci)) console.warn('found another loci') + loci = _loci + } + }) + return loci + } + + function mark(loci: Loci, action: MarkerAction) { + reprMap.forEach((roSet, repr) => repr.mark(loci, action)) + scene.update() + requestDraw() + } + + let nearPlaneDelta = 0 + function computeNearDistance() { + const focusRadius = scene.boundingSphere.radius + let dist = Vec3.distance(controls.target, camera.position) + if (dist > focusRadius) return dist - focusRadius + return 0 + } function render(variant: RenderVariant, force?: boolean) { + // const p = scene.boundingSphere.center + // console.log(p[0], p[1], p[2]) + // Vec3.set(controls.target, p[0], p[1], p[2]) + + const focusRadius = scene.boundingSphere.radius + const targetDistance = Vec3.distance(controls.target, camera.position) + // console.log(targetDistance, controls.target, camera.position) + let near = computeNearDistance() + nearPlaneDelta + camera.near = Math.max(0.01, Math.min(near, targetDistance - 0.5)) + + let fogNear = targetDistance - camera.near + 1 * focusRadius - nearPlaneDelta; + let fogFar = targetDistance - camera.near + 2 * focusRadius - nearPlaneDelta; + + //console.log(fogNear, fogFar); + camera.fogNear = Math.max(fogNear, 0.1); + camera.fogFar = Math.max(fogFar, 0.2); + + // console.log(camera.fogNear, camera.fogFar, targetDistance) + + switch (variant) { + case 'pickObject': objectPickTarget.bind(); break; + case 'pickInstance': instancePickTarget.bind(); break; + case 'pickElement': elementPickTarget.bind(); break; + case 'draw': + ctx.unbindFramebuffer(); + renderer.setViewport(0, 0, canvas.width, canvas.height); + break; + } let didRender = false controls.update() camera.update() - if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value)) { + scene.update() + if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value) || !Mat4.areEqual(scene.view, prevSceneView, EPSILON.Value)) { + // console.log('foo', force, prevSceneView, scene.view) Mat4.copy(prevProjectionView, camera.projectionView) + Mat4.copy(prevSceneView, scene.view) renderer.render(scene, variant) if (variant === 'draw') { pickDirty = true @@ -158,9 +188,6 @@ namespace Viewer { } function draw(force?: boolean) { - ctx.unbindFramebuffer() - const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height } - renderer.setViewport(viewport) if (render('draw', force)) { didDraw.next(performance.now() - startTime) } @@ -179,13 +206,8 @@ namespace Viewer { } function pick() { - objectPickTarget.bind() render('pickObject', pickDirty) - - instancePickTarget.bind() render('pickInstance', pickDirty) - - elementPickTarget.bind() render('pickElement', pickDirty) pickDirty = false @@ -264,6 +286,8 @@ namespace Viewer { animate, pick, identify, + mark, + getLoci, handleResize, resetCamera: () => { @@ -300,10 +324,9 @@ namespace Viewer { function handleResize() { resizeCanvas(canvas, container) - const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height } - renderer.setViewport(viewport) - Viewport.copy(camera.viewport, viewport) - Viewport.copy(controls.viewport, viewport) + renderer.setViewport(0, 0, canvas.width, canvas.height) + Viewport.set(camera.viewport, 0, 0, canvas.width, canvas.height) + Viewport.set(controls.viewport, 0, 0, canvas.width, canvas.height) const pickWidth = Math.round(canvas.width * pickScale) const pickHeight = Math.round(canvas.height * pickScale)