diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index cc5866ae3e7e702f134d72463e4b3d72c7a6cba3..8e2016f1201826b191aa685b34b060bfb12666f4 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -27,7 +27,7 @@ import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; import { Color } from 'mol-util/color'; import { Camera } from './camera'; import { ParamDefinition as PD } from 'mol-util/param-definition'; -import { BoundingSphereHelper } from './helper/bounding-sphere-helper'; +import { BoundingSphereHelper, DebugHelperParams } from './helper/bounding-sphere-helper'; export const Canvas3DParams = { // TODO: FPS cap? @@ -37,9 +37,7 @@ export const Canvas3DParams = { clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }), fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }), pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }), - debug: PD.Group({ - showBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }), - }) + debug: PD.Group(DebugHelperParams) } export type Canvas3DParams = PD.Values<typeof Canvas3DParams> @@ -48,9 +46,6 @@ export { Canvas3D } interface Canvas3D { readonly webgl: WebGLContext, - hide: (repr: Representation.Any) => void - show: (repr: Representation.Any) => void - add: (repr: Representation.Any) => void remove: (repr: Representation.Any) => void update: () => void @@ -128,7 +123,7 @@ namespace Canvas3D { let drawPending = false let lastRenderTime = -1 - const boundingSphereHelper = new BoundingSphereHelper(scene, p.debug.showBoundingSpheres) + const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug) function getLoci(pickingId: PickingId) { let loci: Loci = EmptyLoci @@ -192,16 +187,24 @@ namespace Canvas3D { switch (variant) { case 'pick': objectPickTarget.bind(); + renderer.clear() renderer.render(scene, 'pickObject'); instancePickTarget.bind(); + renderer.clear() renderer.render(scene, 'pickInstance'); groupPickTarget.bind(); + renderer.clear() renderer.render(scene, 'pickGroup'); break; case 'draw': webgl.unbindFramebuffer(); renderer.setViewport(0, 0, canvas.width, canvas.height); + renderer.clear() renderer.render(scene, variant); + if (debugHelper.isEnabled) { + debugHelper.syncVisibility() + renderer.render(debugHelper.scene, 'draw') + } lastRenderTime = now() pickDirty = true break; @@ -298,8 +301,8 @@ namespace Canvas3D { } reprRenderObjects.set(repr, newRO) reprCount.next(reprRenderObjects.size) - boundingSphereHelper.update() scene.update() + if (debugHelper.isEnabled) debugHelper.update() isUpdating = false requestDraw(true) } @@ -309,15 +312,6 @@ namespace Canvas3D { return { webgl, - hide: (repr: Representation.Any) => { - const renderObjectSet = reprRenderObjects.get(repr) - if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = false) - }, - show: (repr: Representation.Any) => { - const renderObjectSet = reprRenderObjects.get(repr) - if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = true) - }, - add: (repr: Representation.Any) => { add(repr) reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => { @@ -335,8 +329,8 @@ namespace Canvas3D { renderObjects.forEach(o => scene.remove(o)) reprRenderObjects.delete(repr) reprCount.next(reprRenderObjects.size) - boundingSphereHelper.update() scene.update() + if (debugHelper.isEnabled) debugHelper.update() isUpdating = false requestDraw(true) } @@ -345,6 +339,7 @@ namespace Canvas3D { clear: () => { reprRenderObjects.clear() scene.clear() + debugHelper.clear() }, // draw, @@ -387,8 +382,8 @@ namespace Canvas3D { if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== renderer.props.pickingAlphaThreshold) { renderer.setPickingAlphaThreshold(props.pickingAlphaThreshold) } - if (props.debug && props.debug.showBoundingSpheres !== undefined) { - boundingSphereHelper.visible = props.debug.showBoundingSpheres + if (props.debug) { + debugHelper.setProps(props.debug) } requestDraw(true) }, @@ -400,9 +395,7 @@ namespace Canvas3D { clip: p.clip, fog: p.fog, pickingAlphaThreshold: renderer.props.pickingAlphaThreshold, - debug: { - showBoundingSpheres: boundingSphereHelper.visible - } + debug: { ...debugHelper.props } } }, get input() { @@ -413,6 +406,7 @@ namespace Canvas3D { }, dispose: () => { scene.clear() + debugHelper.clear() input.dispose() controls.dispose() renderer.dispose() diff --git a/src/mol-canvas3d/helper/bounding-sphere-helper.ts b/src/mol-canvas3d/helper/bounding-sphere-helper.ts index 8bf47ddb6d76dc67d5b0166ab214700adab7fe5c..84bba56ef3442a9043305e96e9dbfc7ab4b43aea 100644 --- a/src/mol-canvas3d/helper/bounding-sphere-helper.ts +++ b/src/mol-canvas3d/helper/bounding-sphere-helper.ts @@ -4,39 +4,105 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { createMeshRenderObject, RenderObject } from 'mol-gl/render-object' import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder'; import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere'; import { Mesh } from 'mol-geo/geometry/mesh/mesh'; -import { Geometry } from 'mol-geo/geometry/geometry'; -import { ValueCell } from 'mol-util'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; import Scene from 'mol-gl/scene'; +import { WebGLContext } from 'mol-gl/webgl/context'; +import { Sphere3D } from 'mol-math/geometry'; + +export const DebugHelperParams = { + sceneBoundingSpheres: PD.Boolean(false, { description: 'Show scene bounding spheres.' }), + objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }), +} +export type DebugHelperParams = typeof DebugHelperParams +export type DebugHelperProps = PD.Values<DebugHelperParams> + +type BoundingSphereData = { boundingSphere: Sphere3D, renderObject: RenderObject } + +// TODO per-object-transform bounding spheres export class BoundingSphereHelper { - private mesh: Mesh - private renderObject: MeshRenderObject + readonly scene: Scene + private readonly parent: Scene + private _props: DebugHelperProps + private objectsData = new Map<RenderObject, BoundingSphereData>() + private sceneData: BoundingSphereData | undefined - constructor(private scene: Scene, visible: boolean) { - this.mesh = MeshBuilder.getMesh(MeshBuilder.createState(1024, 512)) - const values = Mesh.createValuesSimple(this.mesh, { alpha: 0.1, doubleSided: false }) - this.renderObject = createMeshRenderObject(values, { visible, pickable: false, opaque: false }) - scene.add(this.renderObject) + constructor(ctx: WebGLContext, parent: Scene, props: Partial<DebugHelperProps>) { + this.scene = Scene.create(ctx) + this.parent = parent + this._props = { ...PD.getDefaultValues(DebugHelperParams), ...props } } update() { - const builderState = MeshBuilder.createState(1024, 512, this.mesh) - if (this.scene.boundingSphere.radius) { - addSphere(builderState, this.scene.boundingSphere.center, this.scene.boundingSphere.radius, 2) - } - this.scene.forEach(r => { - if (r.boundingSphere.radius) { - addSphere(builderState, r.boundingSphere.center, r.boundingSphere.radius, 2) + const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData) + if (newSceneData) this.sceneData = newSceneData + + const oldRO = new Set<RenderObject>() + this.parent.forEach((r, ro) => { + let objectData = this.objectsData.get(ro) + const newObjectData = updateBoundingSphereData(this.scene, r.boundingSphere, objectData) + if (newObjectData) this.objectsData.set(ro, newObjectData) + oldRO.delete(ro) + }) + oldRO.forEach(ro => { + const objectData = this.objectsData.get(ro) + if (objectData) { + this.scene.remove(objectData.renderObject) + this.objectsData.delete(ro) } }) - this.mesh = MeshBuilder.getMesh(builderState) - ValueCell.update(this.renderObject.values.drawCount, Geometry.getDrawCount(this.mesh)) } - get visible() { return this.renderObject.state.visible } - set visible(value: boolean) { this.renderObject.state.visible = value } + syncVisibility() { + if(this.sceneData) { + this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres + } + + this.parent.forEach((_, ro) => { + const objectData = this.objectsData.get(ro) + if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres + else console.error('expected to have debug render object') + }) + } + + clear() { + this.sceneData = undefined + this.objectsData.clear() + this.scene.clear() + } + + get isEnabled() { + return this._props.sceneBoundingSpheres || this._props.objectBoundingSpheres + } + get props() { return this._props as Readonly<DebugHelperProps> } + + setProps (props: Partial<DebugHelperProps>) { + Object.assign(this._props, props) + if (this.isEnabled) this.update() + } +} + +function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data?: BoundingSphereData) { + if (!data || !Sphere3D.exactEquals(data.boundingSphere, boundingSphere)) { + if (data) scene.remove(data.renderObject) + const renderObject = createBoundingSphereRenderObject(boundingSphere) + scene.add(renderObject) + return { boundingSphere, renderObject } + } +} + +function createBoundingSphereRenderObject(boundingSphere: Sphere3D) { + const builderState = MeshBuilder.createState(1024, 512) + if (boundingSphere.radius) { + addSphere(builderState, boundingSphere.center, boundingSphere.radius, 2) + } else if (isNaN(boundingSphere.radius)) { + console.warn('boundingSphere.radius is NaN') + } + const mesh = MeshBuilder.getMesh(builderState) + const values = Mesh.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }) + return createMeshRenderObject(values, { visible: true, pickable: false, opaque: false }) } \ No newline at end of file diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 691c4ac5e28b089d43348b4b17229bcbd7102ce7..3bf307fc22f671e66f6a67fb9af012c147594b82 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -36,6 +36,7 @@ interface Renderer { readonly stats: RendererStats readonly props: RendererProps + clear: () => void render: (scene: Scene, variant: RenderVariant) => void setViewport: (x: number, y: number, width: number, height: number) => void setClearColor: (color: Color) => void @@ -162,9 +163,6 @@ namespace Renderer { currentProgramId = -1 - gl.depthMask(true) - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) - gl.disable(gl.BLEND) gl.enable(gl.DEPTH_TEST) scene.eachOpaque((r) => renderObject(r, variant)) @@ -177,6 +175,10 @@ namespace Renderer { } return { + clear: () => { + gl.depthMask(true) + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + }, render, setClearColor, diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 941cf4c56c6392a049709425cb18b90491687004..575bc56cb4480d4accc860212aca9d04cca9b57f 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -111,14 +111,10 @@ namespace Scene { renderableMap.forEach(callbackFn) }, eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => { - renderableMap.forEach((r, o) => { - if (r.state.opaque) callbackFn(r, o) - }) + renderableMap.forEach((r, o) => { if (r.state.opaque) callbackFn(r, o) }) }, eachTransparent: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => { - renderableMap.forEach((r, o) => { - if (!r.state.opaque) callbackFn(r, o) - }) + renderableMap.forEach((r, o) => { if (!r.state.opaque) callbackFn(r, o) }) }, get count() { return renderableMap.size