Bladeren bron

very basic handle-helper

Alexander Rose 5 jaren geleden
bovenliggende
commit
eb68ccbf6b

+ 15 - 6
src/mol-canvas3d/canvas3d.ts

@@ -36,6 +36,7 @@ import { Sphere3D } from '../mol-math/geometry';
 import { isDebugMode } from '../mol-util/debug';
 import { CameraHelperParams } from './helper/camera-helper';
 import { produce } from 'immer';
+import { HandleHelper, HandleHelperParams } from './helper/handle-helper';
 
 export const Canvas3DParams = {
     camera: PD.Group({
@@ -60,7 +61,8 @@ export const Canvas3DParams = {
     postprocessing: PD.Group(PostprocessingParams),
     renderer: PD.Group(RendererParams),
     trackball: PD.Group(TrackballControlsParams),
-    debug: PD.Group(DebugHelperParams)
+    debug: PD.Group(DebugHelperParams),
+    handle: PD.Group(HandleHelperParams),
 };
 export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
 export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
@@ -188,12 +190,13 @@ namespace Canvas3D {
         const controls = TrackballControls.create(input, camera, p.trackball);
         const renderer = Renderer.create(webgl, p.renderer);
         const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
+        const handleHelper = new HandleHelper(webgl, p.handle);
         const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
 
-        const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, {
+        const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, handleHelper, {
             cameraHelper: p.camera.helper
         });
-        const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5);
+        const pickPass = new PickPass(webgl, renderer, scene, camera, handleHelper, 0.5);
         const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing);
         const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample);
 
@@ -205,6 +208,7 @@ namespace Canvas3D {
         function getLoci(pickingId: PickingId) {
             let loci: Loci = EmptyLoci;
             let repr: Representation.Any = Representation.Empty;
+            loci = handleHelper.getLoci(pickingId);
             reprRenderObjects.forEach((_, _repr) => {
                 const _loci = _repr.getLoci(pickingId);
                 if (!isEmptyLoci(_loci)) {
@@ -224,10 +228,12 @@ namespace Canvas3D {
             if (repr) {
                 changed = repr.mark(loci, action);
             } else {
+                changed = handleHelper.mark(loci, action);
                 reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
             }
             if (changed) {
                 scene.update(void 0, true);
+                handleHelper.scene.update(void 0, true);
                 const prevPickDirty = pickPass.pickDirty;
                 draw(true);
                 pickPass.pickDirty = prevPickDirty; // marking does not change picking buffers
@@ -428,7 +434,8 @@ namespace Canvas3D {
                 multiSample: { ...multiSample.props },
                 renderer: { ...renderer.props },
                 trackball: { ...controls.props },
-                debug: { ...debugHelper.props }
+                debug: { ...debugHelper.props },
+                handle: { ...handleHelper.props },
             };
         }
 
@@ -535,11 +542,12 @@ namespace Canvas3D {
                 if (props.renderer) renderer.setProps(props.renderer);
                 if (props.trackball) controls.setProps(props.trackball);
                 if (props.debug) debugHelper.setProps(props.debug);
+                if (props.handle) handleHelper.setProps(props.handle);
 
                 requestDraw(true);
             },
             getImagePass: (props: Partial<ImageProps> = {}) => {
-                return new ImagePass(webgl, renderer, scene, camera, debugHelper, props);
+                return new ImagePass(webgl, renderer, scene, camera, debugHelper, handleHelper, props);
             },
 
             get props() {
@@ -563,7 +571,8 @@ namespace Canvas3D {
                     multiSample: { ...multiSample.props },
                     renderer: { ...renderer.props },
                     trackball: { ...controls.props },
-                    debug: { ...debugHelper.props }
+                    debug: { ...debugHelper.props },
+                    handle: { ...handleHelper.props },
                 };
             },
             get input() {

+ 208 - 0
src/mol-canvas3d/helper/handle-helper.ts

@@ -0,0 +1,208 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { WebGLContext } from '../../mol-gl/webgl/context';
+import Scene from '../../mol-gl/scene';
+import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
+import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
+import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
+import { GraphicsRenderObject } from '../../mol-gl/render-object';
+import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
+import { ColorNames } from '../../mol-util/color/names';
+import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
+import { ValueCell } from '../../mol-util';
+import { Sphere3D } from '../../mol-math/geometry';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import produce from 'immer';
+import { Shape } from '../../mol-model/shape';
+import { PickingId } from '../../mol-geo/geometry/picking';
+import { Camera } from '../camera';
+import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
+import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
+import { Visual } from '../../mol-repr/visual';
+import { Interval } from '../../mol-data/int';
+
+const HandleParams = {
+    ...Mesh.Params,
+    alpha: { ...Mesh.Params.alpha, defaultValue: 1 },
+    ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
+    colorX: PD.Color(ColorNames.red, { isEssential: true }),
+    colorY: PD.Color(ColorNames.green, { isEssential: true }),
+    colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
+    scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
+};
+type HandleParams = typeof HandleParams
+type HandleProps = PD.Values<HandleParams>
+
+export const HandleHelperParams = {
+    handle: PD.MappedStatic('off', {
+        on: PD.Group(HandleParams),
+        off: PD.Group({})
+    }, { cycle: true, description: 'Show handle tool' }),
+};
+export type HandleHelperParams = typeof HandleHelperParams
+export type HandleHelperProps = PD.Values<HandleHelperParams>
+
+export class HandleHelper {
+    scene: Scene
+    props: HandleHelperProps = {
+        handle: { name: 'off', params: {} }
+    }
+
+    private renderObject: GraphicsRenderObject | undefined
+
+    private _transform = Mat4();
+    getBoundingSphere(out: Sphere3D, instanceId: number) {
+        if (this.renderObject) {
+            Sphere3D.copy(out, this.renderObject.values.invariantBoundingSphere.ref.value);
+            Mat4.fromArray(this._transform, this.renderObject.values.aTransform.ref.value, instanceId * 16);
+            Sphere3D.transform(out, out, this._transform);
+        }
+        return out;
+    }
+
+    setProps(props: Partial<HandleHelperProps>) {
+        this.props = produce(this.props, p => {
+            if (props.handle !== undefined) {
+                p.handle.name = props.handle.name;
+                if (props.handle.name === 'on') {
+                    this.scene.clear();
+                    const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
+                    this.renderObject = createHandleRenderObject(params);
+                    this.scene.add(this.renderObject);
+                    this.scene.commit();
+
+                    p.handle.params = { ...props.handle.params };
+                }
+            }
+        });
+    }
+
+    get isEnabled() {
+        return this.props.handle.name === 'on';
+    }
+
+    // TODO could be a lists of position/rotation if we want to show more than one handle tool,
+    //      they would be distingishable by their instanceId
+    update(camera: Camera, position: Vec3, rotation: Mat3) {
+        if (!this.renderObject) return;
+
+        Mat4.setTranslation(this.renderObject.values.aTransform.ref.value as unknown as Mat4, position);
+        Mat4.fromMat3(this.renderObject.values.aTransform.ref.value as unknown as Mat4, rotation);
+
+        // TODO make invariant to camera scaling by adjusting renderObject transform
+
+        ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
+        this.scene.update([this.renderObject], true);
+    }
+
+    getLoci(pickingId: PickingId) {
+        const { objectId, groupId, instanceId } = pickingId;
+        if (!this.renderObject || objectId !== this.renderObject.id) return EmptyLoci;
+        return HandleLoci(this, groupId, instanceId);
+    }
+
+    private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
+        if (!this.renderObject) return false;
+        if (!isHandleLoci(loci)) return false;
+        let changed = false;
+        const groupCount = this.renderObject.values.uGroupCount.ref.value;
+        const { elements } = loci;
+        for (const { groupId, instanceId } of elements) {
+            const idx = instanceId * groupCount + groupId;
+            if (apply(Interval.ofSingleton(idx))) changed = true;
+        }
+        return changed;
+    }
+
+    mark(loci: Loci, action: MarkerAction) {
+        if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
+        if (!isHandleLoci(loci)) return false;
+        if (loci.data !== this) return false;
+        return Visual.mark(this.renderObject, loci, action, this.eachGroup);
+    }
+
+    constructor(private webgl: WebGLContext, props: Partial<HandleHelperProps> = {}) {
+        this.scene = Scene.create(webgl);
+        this.setProps(props);
+    }
+}
+
+function createHandleMesh(scale: number, mesh?: Mesh) {
+    const state = MeshBuilder.createState(512, 256, mesh);
+    const radius = 0.05 * scale;
+    const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
+    const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
+    const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
+    const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
+
+    state.currentGroup = HandleGroup.TranslateScreenXY;
+    addSphere(state, Vec3.origin, radius * 3, 2);
+
+    state.currentGroup = HandleGroup.TranslateObjectX;
+    addSphere(state, x, radius, 2);
+    addCylinder(state, Vec3.origin, x, 1, cylinderProps);
+
+    state.currentGroup = HandleGroup.TranslateObjectY;
+    addSphere(state, y, radius, 2);
+    addCylinder(state, Vec3.origin, y, 1, cylinderProps);
+
+    state.currentGroup = HandleGroup.TranslateObjectZ;
+    addSphere(state, z, radius, 2);
+    addCylinder(state, Vec3.origin, z, 1, cylinderProps);
+
+    // TODO add more helper geometries for the other HandleGroup options
+    // TODO add props to create subset of geometries
+
+    return MeshBuilder.getMesh(state);
+}
+
+export const HandleGroup = {
+    None: 0,
+    TranslateScreenXY: 1,
+    // TranslateScreenZ: 2,
+    TranslateObjectX: 3,
+    TranslateObjectY: 4,
+    TranslateObjectZ: 5,
+    // TranslateObjectXY: 6,
+    // TranslateObjectXZ: 7,
+    // TranslateObjectYZ: 8,
+
+    // RotateScreenZ: 9,
+    // RotateObjectX: 10,
+    // RotateObjectY: 11,
+    // RotateObjectZ: 12,
+} as const;
+
+function HandleLoci(handleHelper: HandleHelper, groupId: number, instanceId: number) {
+    return DataLoci('handle', handleHelper, [{ groupId, instanceId }],
+        (boundingSphere: Sphere3D) => handleHelper.getBoundingSphere(boundingSphere, instanceId),
+        () => `Handle Helper | Group Id ${groupId} | Instance Id ${instanceId}`);
+}
+export type HandleLoci = ReturnType<typeof HandleLoci>
+export function isHandleLoci(x: Loci): x is HandleLoci {
+    return x.kind === 'data-loci' && x.tag === 'handle';
+}
+
+function getHandleShape(props: HandleProps, shape?: Shape<Mesh>) {
+    const scale = 10 * props.scale;
+    const mesh = createHandleMesh(scale, shape?.geometry);
+    mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
+    const getColor = (groupId: number) => {
+        switch (groupId) {
+            case HandleGroup.TranslateObjectX: return props.colorX;
+            case HandleGroup.TranslateObjectY: return props.colorY;
+            case HandleGroup.TranslateObjectZ: return props.colorZ;
+            default: return ColorNames.grey;
+        }
+    };
+    return Shape.create('handle', {}, mesh, getColor, () => 1, () => '');
+}
+
+function createHandleRenderObject(props: HandleProps) {
+    const shape = getHandleShape(props);
+    return Shape.createRenderObject(shape, props);
+}

+ 6 - 2
src/mol-canvas3d/passes/draw.ts

@@ -13,6 +13,7 @@ import { Texture } from '../../mol-gl/webgl/texture';
 import { Camera } from '../camera';
 import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { HandleHelper } from '../helper/handle-helper';
 
 export const DrawPassParams = {
     cameraHelper: PD.Group(CameraHelperParams)
@@ -29,7 +30,7 @@ export class DrawPass {
 
     private depthTarget: RenderTarget | null
 
-    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, props: Partial<DrawPassProps> = {}) {
+    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, private handleHelper: HandleHelper, props: Partial<DrawPassProps> = {}) {
         const { gl, extensions, resources } = webgl;
         const width = gl.drawingBufferWidth;
         const height = gl.drawingBufferHeight;
@@ -89,12 +90,15 @@ export class DrawPass {
     }
 
     private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
-        const { renderer, scene, camera, debugHelper, cameraHelper } = this;
+        const { renderer, scene, camera, debugHelper, cameraHelper, handleHelper } = this;
         renderer.render(scene, camera, variant, true, transparentBackground);
         if (debugHelper.isEnabled) {
             debugHelper.syncVisibility();
             renderer.render(debugHelper.scene, camera, variant, false, transparentBackground);
         }
+        if (handleHelper.isEnabled) {
+            renderer.render(handleHelper.scene, camera, variant, false, transparentBackground);
+        }
         if (cameraHelper.isEnabled) {
             cameraHelper.update(camera);
             renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground);

+ 3 - 2
src/mol-canvas3d/passes/image.ts

@@ -15,6 +15,7 @@ import { PostprocessingPass, PostprocessingParams } from './postprocessing';
 import { MultiSamplePass, MultiSampleParams } from './multi-sample';
 import { Camera } from '../camera';
 import { Viewport } from '../camera/util';
+import { HandleHelper } from '../helper/handle-helper';
 
 export const ImageParams = {
     transparentBackground: PD.Boolean(false),
@@ -40,12 +41,12 @@ export class ImagePass {
     get width() { return this._width; }
     get height() { return this._height; }
 
-    constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
+    constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, handleHelper: HandleHelper, props: Partial<ImageProps>) {
         const p = { ...PD.getDefaultValues(ImageParams), ...props };
 
         this._transparentBackground = p.transparentBackground;
 
-        this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass);
+        this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, handleHelper, p.drawPass);
         this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing);
         this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample);
 

+ 7 - 2
src/mol-canvas3d/passes/pick.ts

@@ -11,6 +11,7 @@ import Scene from '../../mol-gl/scene';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { decodeFloatRGB } from '../../mol-util/float-packing';
 import { Camera } from '../camera';
+import { HandleHelper } from '../helper/handle-helper';
 
 export class PickPass {
     pickDirty = true
@@ -27,7 +28,7 @@ export class PickPass {
     private pickWidth: number
     private pickHeight: number
 
-    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private pickBaseScale: number) {
+    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private handleHelper: HandleHelper, private pickBaseScale: number) {
         const { gl } = webgl;
         const width = gl.drawingBufferWidth;
         const height = gl.drawingBufferHeight;
@@ -65,14 +66,18 @@ export class PickPass {
     }
 
     render() {
-        const { renderer, scene, camera } = this;
+        const { renderer, scene, camera, handleHelper: { scene: handleScene } } = this;
         renderer.setViewport(0, 0, this.pickWidth, this.pickHeight);
+
         this.objectPickTarget.bind();
         renderer.render(scene, camera, 'pickObject', true, false);
+        renderer.render(handleScene, camera, 'pickObject', false, false);
         this.instancePickTarget.bind();
         renderer.render(scene, camera, 'pickInstance', true, false);
+        renderer.render(handleScene, camera, 'pickInstance', false, false);
         this.groupPickTarget.bind();
         renderer.render(scene, camera, 'pickGroup', true, false);
+        renderer.render(handleScene, camera, 'pickGroup', false, false);
 
         this.pickDirty = false;
     }