Browse Source

wip, seperating inputs from controls

Alexander Rose 7 years ago
parent
commit
d4670e0e7d

+ 4 - 0
src/mol-gl/camera/util.ts

@@ -20,6 +20,10 @@ export namespace Viewport {
     export function clone(viewport: Viewport): Viewport {
         return { ...viewport }
     }
+
+    export function copy(target: Viewport, source: Viewport): Viewport {
+        return Object.assign(target, source)
+    }
 }
 
 const tmpVec3 = Vec3.zero()

+ 0 - 237
src/mol-gl/controls/orbit.ts

@@ -1,237 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { map, filter, scan } from 'rxjs/operators';
-
-import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra';
-import { clamp } from 'mol-math/interpolate';
-import InputObserver from 'mol-util/input/input-observer';
-
-const Y_UP = Vec3.create(0, 1, 0)
-const tmpVec3 = Vec3.zero()
-
-function cameraLookAt (direction: Vec3, up: Vec3, position: Vec3, target: Vec3) {
-    Vec3.copy(direction, target)
-    Vec3.sub(direction, direction, position)
-    Vec3.normalize(direction, direction)
-}
-
-export const DefaultOrbitControlsProps = {
-    parent: window as Window | Element,
-    noScroll: true,
-
-    phi: Math.PI / 2,
-    theta: 0,
-
-    position: Vec3.zero(),
-    up: Vec3.create(0, 1, 0),
-    target: Vec3.zero(),
-
-    distance: undefined as (number|undefined),
-    damping: 0.25,
-    rotateSpeed: 0.28,
-    zoomSpeed: 0.0075,
-    pinchSpeed: 0.0075,
-    translateSpeed: 1.0,
-}
-export type OrbitControlsProps = Partial<typeof DefaultOrbitControlsProps>
-
-interface OrbitControls {
-    update: () => void
-    copyInto: (positionOut: Vec3, directionOut: Vec3, upOut: Vec3) => void
-
-    position: Vec3
-    direction: Vec3
-    up: Vec3
-    target: Vec3
-
-    distance: number
-    damping: number
-    rotateSpeed: number
-    zoomSpeed: number
-    pinchSpeed: number
-    translateSpeed: number
-
-    phi: number
-    theta: number
-}
-
-namespace OrbitControls {
-    export function create (element: Element, props: OrbitControlsProps = {}): OrbitControls {
-        const p = { ...DefaultOrbitControlsProps, ...props }
-
-        const inputDelta = Vec3.zero() // x, y, zoom
-        const offset = Vec3.zero()
-
-        const upQuat = Quat.identity()
-        const upQuatInverse = Quat.identity()
-        const translateVec3 = Vec3.zero()
-
-        const position = Vec3.clone(p.position)
-        const direction = Vec3.zero()
-        const up = Vec3.clone(p.up)
-        const target = Vec3.clone(p.target)
-
-        // const phiBounds = Vec2.create(0, Math.PI)
-        const phiBounds = Vec2.create(-Infinity, Infinity)
-        const thetaBounds = Vec2.create(-Infinity, Infinity)
-        const distanceBounds = Vec2.create(0, Infinity)
-
-        let { damping, rotateSpeed, zoomSpeed, pinchSpeed, translateSpeed, phi, theta } = p
-        let distance = 0
-
-        // Compute distance if not defined in user options
-        if (p.distance === undefined) {
-            Vec3.sub(tmpVec3, position, target)
-            distance = Vec3.magnitude(tmpVec3)
-        }
-
-        const input = InputObserver.create(element, {
-            parent: p.parent,
-            noScroll: p.noScroll
-        })
-        input.drag.pipe(filter(v => v.buttons === 1)).subscribe(inputRotate)
-        input.drag.pipe(filter(v => v.buttons === 4)).subscribe(inputTranslate)
-        input.wheel.subscribe(inputZoom)
-        input.pinch.subscribe(inputPinch)
-
-        // Apply an initial phi and theta
-        applyPhiTheta()
-
-        return {
-            update,
-            copyInto,
-
-            position,
-            direction,
-            up,
-            target,
-
-            get distance() { return distance },
-            set distance(value: number ) { distance = value },
-            get damping() { return damping },
-            set damping(value: number ) { damping = value },
-            get rotateSpeed() { return rotateSpeed },
-            set rotateSpeed(value: number ) { rotateSpeed = value },
-            get zoomSpeed() { return zoomSpeed },
-            set zoomSpeed(value: number ) { zoomSpeed = value },
-            get pinchSpeed() { return pinchSpeed },
-            set pinchSpeed(value: number ) { pinchSpeed = value },
-            get translateSpeed() { return translateSpeed },
-            set translateSpeed(value: number ) { translateSpeed = value },
-
-            get phi() { return phi },
-            set phi(value: number ) { phi = value; applyPhiTheta() },
-            get theta() { return theta },
-            set theta(value: number ) { theta = value; applyPhiTheta() },
-        }
-
-        function copyInto(positionOut: Vec3, directionOut: Vec3, upOut: Vec3) {
-            Vec3.copy(positionOut, position)
-            Vec3.copy(directionOut, direction)
-            Vec3.copy(upOut, up)
-        }
-
-        function inputRotate ({ dx, dy }: { dx: number, dy: number }) {
-            const PI2 = Math.PI * 2
-            inputDelta[0] -= PI2 * dx * rotateSpeed
-            inputDelta[1] -= PI2 * dy * rotateSpeed
-        }
-
-        function inputZoom ({ dy }: { dy: number }) {
-            inputDelta[2] += dy * zoomSpeed
-        }
-
-        function inputPinch (delta: number) {
-            inputDelta[2] -= delta * pinchSpeed
-        }
-
-        function inputTranslate ({ dx, dy }: { dx: number, dy: number }) {
-            // TODO
-            console.log('translate', { dx, dy })
-            const x = dx * translateSpeed * distance
-            const y = dy * translateSpeed * distance
-            // Vec3.set(translateVec3, x, y, 0)
-            // Vec3.transformQuat(translateVec3, translateVec3, upQuat)
-
-            // pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
-            // pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
-
-            Vec3.copy(translateVec3, position)
-            Vec3.cross(translateVec3, translateVec3, up)
-            Vec3.normalize(translateVec3, translateVec3)
-            Vec3.scale(translateVec3, translateVec3, x )
-
-            const up2 = Vec3.clone(up)
-            Vec3.normalize(up2, up2)
-            Vec3.scale(up2, up2, y )
-            Vec3.add(translateVec3, translateVec3, up2)
-
-            Vec3.add(target, target, translateVec3)
-            Vec3.add(position, position, translateVec3)
-        }
-
-        function updateDirection () {
-            Quat.fromUnitVec3(upQuat, up, Y_UP)
-            Quat.invert(upQuatInverse, upQuat)
-
-            Vec3.sub(offset, position, target)
-            Vec3.transformQuat(offset, offset, upQuat)
-
-            let _distance = distance
-            let _theta = Math.atan2(offset[0], offset[2])
-            let _phi = Math.atan2(Math.sqrt(offset[0] * offset[0] + offset[2] * offset[2]), offset[1])
-
-            _theta += inputDelta[0]
-            _phi += inputDelta[1]
-
-            _theta = clamp(_theta, thetaBounds[0], thetaBounds[1])
-            _phi = clamp(_phi, phiBounds[0], phiBounds[1])
-            _phi = clamp(_phi, EPSILON.Value, Math.PI - EPSILON.Value)
-
-            _distance += inputDelta[2]
-            _distance = clamp(_distance, distanceBounds[0], distanceBounds[1])
-
-            const radius = Math.abs(_distance) <= EPSILON.Value ? EPSILON.Value : _distance
-            offset[0] = radius * Math.sin(_phi) * Math.sin(_theta)
-            offset[1] = radius * Math.cos(_phi)
-            offset[2] = radius * Math.sin(_phi) * Math.cos(_theta)
-
-            phi = _phi
-            theta = _theta
-            distance = _distance
-
-            Vec3.transformQuat(offset, offset, upQuatInverse)
-            Vec3.add(position, target, offset)
-            cameraLookAt(direction, up, position, target)
-        }
-
-        function update () {
-            updateDirection()
-            for (let i = 0; i < inputDelta.length; i++) {
-                inputDelta[i] *= 1 - damping
-            }
-        }
-
-        function applyPhiTheta () {
-            let _phi = phi
-            let _theta = theta
-            _theta = clamp(_theta, thetaBounds[0], thetaBounds[1])
-            _phi = clamp(_phi, phiBounds[0], phiBounds[1])
-            _phi = clamp(_phi, EPSILON.Value, Math.PI - EPSILON.Value)
-
-            const dist = Math.max(EPSILON.Value, distance)
-            position[0] = dist * Math.sin(_phi) * Math.sin(_theta)
-            position[1] = dist * Math.cos(_phi)
-            position[2] = dist * Math.sin(_phi) * Math.cos(_theta)
-            Vec3.add(position, position, target)
-
-            updateDirection()
-        }
-    }
-}
-
-export default OrbitControls

+ 131 - 226
src/mol-gl/controls/trackball.ts

@@ -9,16 +9,15 @@
  * copyright (c) 2010-2018 three.js authors. MIT License
  */
 
-import { Subject } from 'rxjs';
-
 import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra';
-import { clamp } from 'mol-math/interpolate';
-import InputObserver from 'mol-util/input/input-observer';
-import { cameraLookAt } from '../camera/util';
+import { cameraLookAt, Viewport } from '../camera/util';
+import InputObserver, { DragInput, WheelInput, MouseButtonsFlag, PinchInput } from 'mol-util/input/input-observer';
 
 export const DefaultTrackballControlsProps = {
+    noScroll: true,
+
     rotateSpeed: 3.0,
-    zoomSpeed: 2.2,
+    zoomSpeed: 2.0,
     panSpeed: 0.1,
 
     staticMoving: false,
@@ -29,32 +28,14 @@ export const DefaultTrackballControlsProps = {
 }
 export type TrackballControlsProps = Partial<typeof DefaultTrackballControlsProps>
 
-const enum STATE {
-    NONE = - 1,
-    ROTATE = 0,
-    ZOOM = 1,
-    PAN = 2,
-    TOUCH_ROTATE = 3,
-    TOUCH_ZOOM_PAN = 4
-}
-
 interface Object {
     position: Vec3,
     direction: Vec3,
     up: Vec3,
 }
 
-interface Screen {
-    left: number
-    top: number
-    width: number
-    height: number
-}
-
 interface TrackballControls {
-    change: Subject<void>
-    start: Subject<void>
-    end: Subject<void>
+    viewport: Viewport
 
     dynamicDampingFactor: number
     rotateSpeed: number
@@ -62,7 +43,6 @@ interface TrackballControls {
     panSpeed: number
 
     update: () => void
-    handleResize: () => void
     reset: () => void
     dispose: () => void
 }
@@ -71,23 +51,23 @@ namespace TrackballControls {
     export function create (element: Element, object: Object, props: TrackballControlsProps = {}): TrackballControls {
         const p = { ...DefaultTrackballControlsProps, ...props }
 
-        const screen: Screen = { left: 0, top: 0, width: 0, height: 0 }
+        const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }
 
         let { rotateSpeed, zoomSpeed, panSpeed } = p
         let { staticMoving, dynamicDampingFactor } = p
         let { minDistance, maxDistance } = p
 
-        const change = new Subject<void>()
-        const start = new Subject<void>()
-        const end = new Subject<void>()
+        let disposed = false
+
+        const input = InputObserver.create(element)
+        input.drag.subscribe(onDrag)
+        input.wheel.subscribe(onWheel)
+        input.pinch.subscribe(onPinch)
 
         // internals
         const target = Vec3.zero()
         const lastPosition = Vec3.zero()
 
-        let _state = STATE.NONE
-        let _prevState = STATE.NONE
-
         const _eye = Vec3.zero()
 
         const _movePrev = Vec2.zero()
@@ -99,8 +79,8 @@ namespace TrackballControls {
         const _zoomStart = Vec2.zero()
         const _zoomEnd = Vec2.zero()
 
-        let _touchZoomDistanceStart = 0
-        let _touchZoomDistanceEnd = 0
+        // let _touchZoomDistanceStart = 0
+        // let _touchZoomDistanceEnd = 0
 
         const _panStart = Vec2.zero()
         const _panEnd = Vec2.zero()
@@ -110,30 +90,12 @@ namespace TrackballControls {
         const position0 = Vec3.clone(object.position)
         const up0 = Vec3.clone(object.up)
 
-        // methods
-        function handleResize () {
-            if ( element instanceof Document ) {
-                screen.left = 0;
-                screen.top = 0;
-                screen.width = window.innerWidth;
-                screen.height = window.innerHeight;
-            } else {
-                const box = element.getBoundingClientRect();
-                // adjustments come from similar code in the jquery offset() function
-                const d = element.ownerDocument.documentElement;
-                screen.left = box.left + window.pageXOffset - d.clientLeft;
-                screen.top = box.top + window.pageYOffset - d.clientTop;
-                screen.width = box.width;
-                screen.height = box.height;
-            }
-        }
-
         const mouseOnScreenVec2 = Vec2.zero()
         function getMouseOnScreen(pageX: number, pageY: number) {
             Vec2.set(
                 mouseOnScreenVec2,
-                (pageX - screen.left) / screen.width,
-                (pageY - screen.top) / screen.height
+                (pageX - viewport.x) / viewport.width,
+                (pageY - viewport.y) / viewport.height
             );
             return mouseOnScreenVec2;
         }
@@ -142,8 +104,8 @@ namespace TrackballControls {
         function getMouseOnCircle(pageX: number, pageY: number) {
             Vec2.set(
                 mouseOnCircleVec2,
-                ((pageX - screen.width * 0.5 - screen.left) / (screen.width * 0.5)),
-                ((screen.height + 2 * (screen.top - pageY)) / screen.width) // screen.width intentional
+                ((pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5)),
+                ((viewport.height + 2 * (viewport.y - pageY)) / viewport.width) // screen.width intentional
             );
             return mouseOnCircleVec2;
         }
@@ -196,21 +158,32 @@ namespace TrackballControls {
 
 
         function zoomCamera () {
-            if (_state === STATE.TOUCH_ZOOM_PAN) {
-                const factor = _touchZoomDistanceStart / _touchZoomDistanceEnd
-                _touchZoomDistanceStart = _touchZoomDistanceEnd;
+            // if (_state === STATE.TOUCH_ZOOM_PAN) {
+            //     const factor = _touchZoomDistanceStart / _touchZoomDistanceEnd
+            //     _touchZoomDistanceStart = _touchZoomDistanceEnd;
+            //     Vec3.scale(_eye, _eye, factor)
+            // } else {
+            //     const factor = 1.0 + ( _zoomEnd[1] - _zoomStart[1] ) * zoomSpeed
+            //     if (factor !== 1.0 && factor > 0.0) {
+            //         Vec3.scale(_eye, _eye, factor)
+            //     }
+
+            //     if (staticMoving) {
+            //         Vec2.copy(_zoomStart, _zoomEnd)
+            //     } else {
+            //         _zoomStart[1] += ( _zoomEnd[1] - _zoomStart[1] ) * dynamicDampingFactor
+            //     }
+            // }
+
+            const factor = 1.0 + ( _zoomEnd[1] - _zoomStart[1] ) * zoomSpeed
+            if (factor !== 1.0 && factor > 0.0) {
                 Vec3.scale(_eye, _eye, factor)
-            } else {
-                const factor = 1.0 + ( _zoomEnd[1] - _zoomStart[1] ) * zoomSpeed
-                if (factor !== 1.0 && factor > 0.0) {
-                    Vec3.scale(_eye, _eye, factor)
-                }
+            }
 
-                if (staticMoving) {
-                    Vec2.copy(_zoomStart, _zoomEnd)
-                } else {
-                    _zoomStart[1] += ( _zoomEnd[1] - _zoomStart[1] ) * dynamicDampingFactor
-                }
+            if (staticMoving) {
+                Vec2.copy(_zoomStart, _zoomEnd)
+            } else {
+                _zoomStart[1] += ( _zoomEnd[1] - _zoomStart[1] ) * dynamicDampingFactor
             }
         }
 
@@ -271,197 +244,130 @@ namespace TrackballControls {
             cameraLookAt(object.position, object.up, object.direction, target)
 
             if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
-                change.next()
                 Vec3.copy(lastPosition, object.position)
             }
         }
 
         function reset() {
-            _state = STATE.NONE;
-            _prevState = STATE.NONE;
-
             Vec3.copy(target, target0)
             Vec3.copy(object.position, position0)
             Vec3.copy(object.up, up0)
 
             Vec3.sub(_eye, object.position, target)
-
             cameraLookAt(object.position, object.up, object.direction, target)
-
-            change.next()
             Vec3.copy(lastPosition, object.position)
         }
 
         // listeners
 
-        function mousedown(event: MouseEvent) {
-            event.preventDefault();
-            event.stopPropagation();
-
-            if (_state === STATE.NONE) {
-                _state = event.button;
-            }
-
-            if (_state === STATE.ROTATE) {
-                Vec2.copy(_moveCurr, getMouseOnCircle(event.pageX, event.pageY))
-                Vec2.copy(_movePrev, _moveCurr)
-            } else if (_state === STATE.ZOOM) {
-                Vec2.copy(_zoomStart, getMouseOnScreen(event.pageX, event.pageY))
-                Vec2.copy(_zoomEnd, _zoomStart)
-            } else if (_state === STATE.PAN) {
-                Vec2.copy(_panStart, getMouseOnScreen(event.pageX, event.pageY))
-                Vec2.copy(_panEnd, _panStart)
-            }
-
-            document.addEventListener('mousemove', mousemove, false);
-            document.addEventListener('mouseup', mouseup, false);
-
-            start.next()
-        }
-
-        function mousemove(event: MouseEvent) {
-            event.preventDefault();
-            event.stopPropagation();
-
-            if (_state === STATE.ROTATE) {
-                Vec2.copy(_movePrev, _moveCurr)
-                Vec2.copy(_moveCurr, getMouseOnCircle(event.pageX, event.pageY))
-            } else if (_state === STATE.ZOOM) {
-                Vec2.copy(_zoomEnd, getMouseOnScreen(event.pageX, event.pageY))
-            } else if (_state === STATE.PAN) {
-                Vec2.copy(_panEnd, getMouseOnScreen(event.pageX, event.pageY))
-            }
-        }
-
-        function mouseup(event: MouseEvent) {
-            event.preventDefault();
-            event.stopPropagation();
-
-            _state = STATE.NONE;
-
-            document.removeEventListener( 'mousemove', mousemove );
-            document.removeEventListener( 'mouseup', mouseup );
-
-            end.next()
-        }
-
-        function mousewheel( event: MouseWheelEvent ) {
-            event.preventDefault();
-            event.stopPropagation();
-
-            switch ( event.deltaMode ) {
-                case 2:
-                    // Zoom in pages
-                    _zoomStart[1] -= event.deltaY * 0.025;
-                    break;
-                case 1:
-                    // Zoom in lines
-                    _zoomStart[1] -= event.deltaY * 0.01;
-                    break;
-                default:
-                    // undefined, 0, assume pixels
-                    _zoomStart[1] -= event.deltaY * 0.00025;
-                    break;
-            }
-
-            start.next()
-            end.next()
-        }
-
-        function touchstart(event: TouchEvent ) {
-            switch ( event.touches.length ) {
-                case 1:
-                    _state = STATE.TOUCH_ROTATE;
-                    Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
+        function onDrag({ pageX, pageY, buttons, modifiers, started }: DragInput) {
+            if (started) {
+                if (buttons === MouseButtonsFlag.Primary) {
+                    Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
                     Vec2.copy(_movePrev, _moveCurr)
-                    break;
-                default: // 2 or more
-                    _state = STATE.TOUCH_ZOOM_PAN;
-                    const dx = event.touches[0].pageX - event.touches[1].pageX;
-                    const dy = event.touches[0].pageY - event.touches[1].pageY;
-                    _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy);
-
-                    const x = ( event.touches[0].pageX + event.touches[1].pageX) / 2;
-                    const y = ( event.touches[0].pageY + event.touches[1].pageY) / 2;
-                    Vec2.copy(_panStart, getMouseOnScreen(x, y))
+                } else if (buttons === MouseButtonsFlag.Auxilary) {
+                    Vec2.copy(_zoomStart, getMouseOnScreen(pageX, pageY))
+                    Vec2.copy(_zoomEnd, _zoomStart)
+                } else if (buttons === MouseButtonsFlag.Secondary) {
+                    Vec2.copy(_panStart, getMouseOnScreen(pageX, pageY))
                     Vec2.copy(_panEnd, _panStart)
-                    break;
+                }
             }
 
-            start.next()
-        }
-
-        function touchmove(event: TouchEvent) {
-            event.preventDefault();
-            event.stopPropagation();
-
-            switch ( event.touches.length ) {
-                case 1:
-                    Vec2.copy(_movePrev, _moveCurr)
-                    Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
-                    break;
-                default: // 2 or more
-                    const dx = event.touches[0].pageX - event.touches[1].pageX;
-                    const dy = event.touches[0].pageY - event.touches[1].pageY;
-                    _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
-
-                    const x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
-                    const y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
-                    Vec2.copy(_panEnd, getMouseOnScreen(x, y))
-                    break;
+            if (buttons === MouseButtonsFlag.Primary) {
+                Vec2.copy(_movePrev, _moveCurr)
+                Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
+            } else if (buttons === MouseButtonsFlag.Auxilary) {
+                Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY))
+            } else if (buttons === MouseButtonsFlag.Secondary) {
+                Vec2.copy(_panEnd, getMouseOnScreen(pageX, pageY))
             }
         }
 
-        function touchend(event: TouchEvent) {
-            switch ( event.touches.length ) {
-                case 0:
-                    _state = STATE.NONE;
-                    break;
-                case 1:
-                    _state = STATE.TOUCH_ROTATE;
-                    Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
-                    Vec2.copy(_movePrev, _moveCurr)
-                    break;
-
-            }
-
-            end.next()
+        function onWheel({ dy }: WheelInput) {
+            _zoomStart[1] -= dy
         }
 
-        function contextmenu(event: Event) {
-            event.preventDefault();
+        function onPinch({ delta }: PinchInput) {
+            console.log(delta)
+            _zoomStart[1] -= delta
         }
 
-        function dispose() {
-            element.removeEventListener( 'contextmenu', contextmenu, false );
-            element.removeEventListener( 'mousedown', mousedown as any, false );
-            element.removeEventListener( 'wheel', mousewheel, false );
+        // function touchstart(event: TouchEvent ) {
+        //     switch ( event.touches.length ) {
+        //         case 1:
+        //             // _state = STATE.TOUCH_ROTATE;
+        //             Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
+        //             Vec2.copy(_movePrev, _moveCurr)
+        //             break;
+        //         default: // 2 or more
+        //             // _state = STATE.TOUCH_ZOOM_PAN;
+        //             const dx = event.touches[0].pageX - event.touches[1].pageX;
+        //             const dy = event.touches[0].pageY - event.touches[1].pageY;
+        //             _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy);
+
+        //             const x = ( event.touches[0].pageX + event.touches[1].pageX) / 2;
+        //             const y = ( event.touches[0].pageY + event.touches[1].pageY) / 2;
+        //             Vec2.copy(_panStart, getMouseOnScreen(x, y))
+        //             Vec2.copy(_panEnd, _panStart)
+        //             break;
+        //     }
+        // }
+
+        // function touchmove(event: TouchEvent) {
+        //     event.preventDefault();
+        //     event.stopPropagation();
+
+        //     switch ( event.touches.length ) {
+        //         case 1:
+        //             Vec2.copy(_movePrev, _moveCurr)
+        //             Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
+        //             break;
+        //         default: // 2 or more
+        //             const dx = event.touches[0].pageX - event.touches[1].pageX;
+        //             const dy = event.touches[0].pageY - event.touches[1].pageY;
+        //             _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
+
+        //             const x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
+        //             const y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
+        //             Vec2.copy(_panEnd, getMouseOnScreen(x, y))
+        //             break;
+        //     }
+        // }
+
+        // function touchend(event: TouchEvent) {
+        //     switch ( event.touches.length ) {
+        //         case 0:
+        //             // _state = STATE.NONE;
+        //             break;
+        //         case 1:
+        //             // _state = STATE.TOUCH_ROTATE;
+        //             Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
+        //             Vec2.copy(_movePrev, _moveCurr)
+        //             break;
+
+        //     }
+        // }
 
-            element.removeEventListener( 'touchstart', touchstart as any, false );
-            element.removeEventListener( 'touchend', touchend as any, false );
-            element.removeEventListener( 'touchmove', touchmove as any, false );
+        function dispose() {
+            if (disposed) return
+            disposed = true
+            input.dispose()
 
-            document.removeEventListener( 'mousemove', mousemove, false );
-            document.removeEventListener( 'mouseup', mouseup, false );
+            // element.removeEventListener( 'touchstart', touchstart as any, false );
+            // element.removeEventListener( 'touchend', touchend as any, false );
+            // element.removeEventListener( 'touchmove', touchmove as any, false );
         }
 
-        element.addEventListener( 'contextmenu', contextmenu, false );
-        element.addEventListener( 'mousedown', mousedown as any, false );
-        element.addEventListener( 'wheel', mousewheel, false );
-
-        element.addEventListener( 'touchstart', touchstart as any, false );
-        element.addEventListener( 'touchend', touchend as any, false );
-        element.addEventListener( 'touchmove', touchmove as any, false );
-
-        handleResize();
+        // element.addEventListener( 'touchstart', touchstart as any, false );
+        // element.addEventListener( 'touchend', touchend as any, false );
+        // element.addEventListener( 'touchmove', touchmove as any, false );
 
         // force an update at start
         update();
 
         return {
-            change,
-            start,
-            end,
+            viewport,
 
             get dynamicDampingFactor() { return dynamicDampingFactor },
             set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value },
@@ -473,7 +379,6 @@ namespace TrackballControls {
             set panSpeed(value: number ) { panSpeed = value },
 
             update,
-            handleResize,
             reset,
             dispose
         }

+ 17 - 14
src/mol-gl/renderer.ts

@@ -13,7 +13,7 @@ import Stats from './stats'
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { ValueCell } from 'mol-util';
 import TrackballControls from './controls/trackball';
-import { element } from 'mol-util/input/mouse-event';
+import { Viewport } from './camera/util';
 
 let _renderObjectId = 0;
 function getNextId() {
@@ -55,6 +55,7 @@ interface Renderer {
     clear: () => void
     draw: () => void
     frame: () => void
+    handleResize: () => void
 }
 
 function resizeCanvas (canvas: HTMLCanvasElement, element: HTMLElement) {
@@ -102,10 +103,7 @@ namespace Renderer {
         const objectIdRenderableMap: { [k: number]: Renderable } = {}
 
         const extensions = [
-            'OES_texture_float',
-            'OES_texture_float_linear',
             'OES_element_index_uint',
-            'EXT_blend_minmax',
             'ANGLE_instanced_arrays'
         ]
         const optionalExtensions = [
@@ -116,26 +114,20 @@ namespace Renderer {
 
         const camera = PerspectiveCamera.create({
             near: 0.01,
-            far: 1000,
+            far: 10000,
             position: Vec3.create(0, 0, 50)
         })
 
         const controls = TrackballControls.create(canvas, camera, {
-            
-        })
 
-        // window.addEventListener('resize', (ev: Event) => {
-        //     console.log({ viewport: { x: 0, y: 0, width: canvas.width, height: canvas.height }})
-        //     regl({ viewport: { x: 0, y: 0, width: canvas.width, height: canvas.height }})
-        // }, false)
+        })
 
         const baseContext = regl({
-            viewport: { x: 0, y: 0, width: canvas.width, height: canvas.height },
             context: {
                 model: Mat4.identity(),
                 transform: Mat4.identity(),
                 view: camera.view,
-                projection: camera.projection
+                projection: camera.projection,
             },
             uniforms: {
                 model: regl.context('model' as any),
@@ -168,6 +160,9 @@ namespace Renderer {
             })
         }
 
+        window.addEventListener('resize', handleResize, false)
+        handleResize()
+
         // TODO animate, draw, requestDraw
         return {
             camera,
@@ -200,7 +195,15 @@ namespace Renderer {
             draw,
             frame: () => {
                 regl.frame((ctx) => draw())
-            }
+            },
+            handleResize
+        }
+
+        function handleResize() {
+            const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height }
+            regl({ viewport })
+            Viewport.copy(camera.viewport, viewport)
+            Viewport.copy(controls.viewport, viewport)
         }
     }
 }

+ 12 - 0
src/mol-math/linear-algebra/3d/vec2.ts

@@ -75,6 +75,18 @@ namespace Vec2 {
         return out;
     }
 
+    export function mul(out: Vec2, a: Vec2, b: Vec2) {
+        out[0] = a[0] * b[0];
+        out[1] = a[1] * b[1];
+        return out;
+    }
+
+    export function div(out: Vec2, a: Vec2, b: Vec2) {
+        out[0] = a[0] / b[0];
+        out[1] = a[1] / b[1];
+        return out;
+    }
+
     export function scale(out: Vec2, a: Vec2, b: number) {
         out[0] = a[0] * b;
         out[1] = a[1] * b;

+ 14 - 0
src/mol-math/linear-algebra/3d/vec3.ts

@@ -94,6 +94,20 @@ namespace Vec3 {
         return out;
     }
 
+    export function mul(out: Vec3, a: Vec3, b: Vec3) {
+        out[0] = a[0] * b[0];
+        out[1] = a[1] * b[1];
+        out[2] = a[2] * b[2];
+        return out;
+    }
+
+    export function div(out: Vec3, a: Vec3, b: Vec3) {
+        out[0] = a[0] / b[0];
+        out[1] = a[1] / b[1];
+        out[2] = a[2] / b[2];
+        return out;
+    }
+
     export function scale(out: Vec3, a: Vec3, b: number) {
         out[0] = a[0] * b;
         out[1] = a[1] * b;

+ 1 - 1
src/mol-util/input/event-offset.ts

@@ -23,7 +23,7 @@ export function eventOffset (out: Vec2, ev: MouseEvent | Touch, target: Element)
 }
 
 function getBoundingClientOffset (element: Element | Window | Document) {
-    if (element !== window && element !== document && element !== document.body) {
+    if (element instanceof Window || element instanceof Document || element === document.body) {
         return rootPosition
     } else {
         return (element as Element).getBoundingClientRect()

+ 283 - 109
src/mol-util/input/input-observer.ts

@@ -8,11 +8,10 @@ import { Subject } from 'rxjs';
 
 import { Vec2 } from 'mol-math/linear-algebra';
 
-import MouseWheel from './mouse-wheel'
+import toPixels from '../to-pixels'
 import TouchPinch from './touch-pinch'
-import { eventOffset } from './event-offset'
 
-export function getButtons(event: MouseEvent | Touch) {
+function getButtons(event: MouseEvent | Touch) {
     if (typeof event === 'object') {
         if ('buttons' in event) {
             return event.buttons
@@ -40,133 +39,193 @@ export function getButtons(event: MouseEvent | Touch) {
 }
 
 export const DefaultInputObserverProps = {
-    parent: window as Window | Element,
-    noScroll: true
+    noScroll: true,
+    noContextMenu: true
 }
 export type InputObserverProps = Partial<typeof DefaultInputObserverProps>
 
-export type MouseModifiers = {
+export type ModifiersKeys = {
     shift: boolean,
     alt: boolean,
     control: boolean,
     meta: boolean
 }
 
+export const enum MouseButtonsFlag {
+    /** No button or un-initialized */
+    None = 0x0,
+    /** Primary button (usually left) */
+    Primary = 0x1,
+    /** Secondary button (usually right) */
+    Secondary = 0x2,
+    /** Auxilary button (usually middle or mouse wheel button)  */
+    Auxilary = 0x4,
+    /** 4th button (typically the "Browser Back" button) */
+    Forth = 0x8,
+    /** 5th button (typically the "Browser Forward" button) */
+    Five = 0x10,
+}
+
+type BaseInput = {
+    buttons: number
+    modifiers: ModifiersKeys
+}
+
+export type DragInput = {
+    x: number,
+    y: number,
+    dx: number,
+    dy: number,
+    pageX: number,
+    pageY: number,
+    started: boolean
+} & BaseInput
+
+export type WheelInput = {
+    dx: number,
+    dy: number,
+    dz: number,
+} & BaseInput
+
+export type ClickInput = {
+    x: number,
+    y: number,
+    pageX: number,
+    pageY: number,
+} & BaseInput
+
+export type PinchInput = {
+    delta: number,
+    distance: number
+}
+
+const enum DraggingState {
+    Stopped = 0,
+    Started = 1,
+    Moving = 2
+}
+
+type PointerEvent = {
+    clientX: number
+    clientY: number
+    pageX: number
+    pageY: number
+}
+
 interface InputObserver {
     noScroll: boolean
-    isDragging: () => boolean
-    isPinching: () => boolean
+    noContextMenu: boolean
 
-    drag: Subject<{ dx: number, dy: number, buttons: number, modifiers: MouseModifiers }>,
-    wheel: Subject<{ dx: number, dy: number, dz: number, event: WheelEvent }>,
-    pinch: Subject<number>,
-    // click: Subject<{ x: number, y: number, buttons: number, modifiers: MouseModifiers }>,
+    drag: Subject<DragInput>,
+    wheel: Subject<WheelInput>,
+    pinch: Subject<PinchInput>,
+    click: Subject<ClickInput>,
 
     dispose: () => void
 }
 
 namespace InputObserver {
     export function create (element: Element, props: InputObserverProps = {}): InputObserver {
-        const { parent, noScroll } = { ...DefaultInputObserverProps, ...props }
+        let { noScroll, noContextMenu } = { ...DefaultInputObserverProps, ...props }
 
-        const mouseStart = Vec2.zero()
-        const tmp = Vec2.zero()
-        const tmp2 = Vec2.zero()
-        const modifiers: MouseModifiers = {
+        const lineHeight = toPixels('ex', element)
+
+        let lastTouchDistance = 0
+        const pointerStart = Vec2.zero()
+        const pointerEnd = Vec2.zero()
+        const pointerDelta = Vec2.zero()
+        const rectSize = Vec2.zero()
+        const modifiers: ModifiersKeys = {
             shift: false,
             alt: false,
             control: false,
             meta: false
         }
 
-        const touchPinch = TouchPinch.create(element)
-        const mouseWheel = MouseWheel.create(element, noScroll)
+        // const touchPinch = TouchPinch.create(element)
 
-        let dragging = false
+        let dragging: DraggingState = DraggingState.Stopped
         let disposed = false
         let buttons = 0
 
-        const drag = new Subject<{ dx: number, dy: number, buttons: number, modifiers: MouseModifiers }>()
-        const wheel = mouseWheel.wheel
-        const pinch = new Subject<number>()
+        const drag = new Subject<DragInput>()
+        const click = new Subject<ClickInput>()
+        const wheel = new Subject<WheelInput>()
+        const pinch = new Subject<PinchInput>()
 
         attach()
 
         return {
-            get noScroll () { return mouseWheel.noScroll },
-            set noScroll (value: boolean) { mouseWheel.noScroll = value },
-            isDragging: () => dragging,
-            isPinching,
+            get noScroll () { return noScroll },
+            set noScroll (value: boolean) { noScroll = value },
+            get noContextMenu () { return noContextMenu },
+            set noContextMenu (value: boolean) { noContextMenu = value },
 
             drag,
             wheel,
             pinch,
+            click,
 
             dispose
         }
 
         function attach () {
-            element.addEventListener('mousedown', onInputDown as any, false)
+            element.addEventListener( 'contextmenu', onContextMenu, false )
 
+            element.addEventListener('wheel', onMouseWheel, false)
+            element.addEventListener('mousedown', onPointerDown as any, false)
             // for dragging to work outside canvas bounds,
-            // mouse move/up events have to be added to parent, i.e. window
-            parent.addEventListener('mousemove', onInputMove as any, false)
-            parent.addEventListener('mouseup', onInputUp as any, false)
-
-            // don't allow simulated mouse events
-            element.addEventListener('touchstart', preventDefault as any, false)
+            // mouse move/up events have to be added to a parent, i.e. window
+            window.addEventListener('mousemove', onMouseMove as any, false)
+            window.addEventListener('mouseup', onPointerUp as any, false)
 
+            element.addEventListener('touchstart', onTouchStart as any, false)
             element.addEventListener('touchmove', onTouchMove as any, false)
+            element.addEventListener('touchend', onTouchEnd as any, false)
 
-            touchPinch.place.subscribe(onPinchPlace)
-            touchPinch.lift.subscribe(onPinchLift)
-            touchPinch.change.subscribe(onPinchChange)
+            // touchPinch.place.subscribe(onPinchPlace)
+            // touchPinch.lift.subscribe(onPinchLift)
+            // touchPinch.change.subscribe(onPinchChange)
 
             element.addEventListener('blur', handleBlur)
             element.addEventListener('keyup', handleMods as EventListener)
             element.addEventListener('keydown', handleMods as EventListener)
             element.addEventListener('keypress', handleMods as EventListener)
-
-            if (!(element instanceof Window)) {
-                window.addEventListener('blur', handleBlur)
-                window.addEventListener('keyup', handleMods)
-                window.addEventListener('keydown', handleMods)
-                window.addEventListener('keypress', handleMods)
-            }
         }
 
         function dispose () {
             if (disposed) return
             disposed = true
 
-            mouseWheel.dispose()
-            touchPinch.dispose()
+            // touchPinch.dispose()
 
-            element.removeEventListener('touchstart', preventDefault as any, false)
-            element.removeEventListener('touchmove', onTouchMove as any, false)
+            element.removeEventListener( 'contextmenu', onContextMenu, false )
 
-            element.removeEventListener('mousedown', onInputDown as any, false)
+            element.removeEventListener('wheel', onMouseWheel, false)
+            element.removeEventListener('mousedown', onMouseDown as any, false)
+            window.removeEventListener('mousemove', onMouseMove as any, false)
+            window.removeEventListener('mouseup', onMouseUp as any, false)
 
-            parent.removeEventListener('mousemove', onInputMove as any, false)
-            parent.removeEventListener('mouseup', onInputUp as any, false)
+            element.removeEventListener('touchstart', onTouchStart as any, false)
+            element.removeEventListener('touchmove', onTouchMove as any, false)
+            element.removeEventListener('touchend', onTouchEnd as any, false)
 
             element.removeEventListener('blur', handleBlur)
             element.removeEventListener('keyup', handleMods as EventListener)
             element.removeEventListener('keydown', handleMods as EventListener)
             element.removeEventListener('keypress', handleMods as EventListener)
-
-            if (!(element instanceof Window)) {
-                window.removeEventListener('blur', handleBlur)
-                window.removeEventListener('keyup', handleMods)
-                window.removeEventListener('keydown', handleMods)
-                window.removeEventListener('keypress', handleMods)
-            }
         }
 
         function preventDefault (ev: Event | Touch) {
             if ('preventDefault' in ev) ev.preventDefault()
         }
 
+        function onContextMenu(event: Event) {
+            if (noContextMenu) {
+                event.preventDefault()
+            }
+        }
+
         function handleBlur () {
             if (buttons || modifiers.shift || modifiers.alt || modifiers.meta || modifiers.control) {
                 buttons = 0
@@ -181,72 +240,182 @@ namespace InputObserver {
             if ('metaKey' in event) modifiers.meta = !!event.metaKey
         }
 
-        function onTouchMove (ev: TouchEvent) {
-            if (!dragging || isPinching()) return
-
-            // find currently active finger
-            for (let i = 0; i < ev.changedTouches.length; i++) {
-                const changed = ev.changedTouches[i]
-                const idx = touchPinch.indexOfTouch(changed)
-                if (idx !== -1) {
-                    onInputMove(changed)
-                    break
-                }
+        function getCenterTouch (ev: TouchEvent): PointerEvent {
+            const t0 = ev.touches[0]
+            const t1 = ev.touches[1]
+            return {
+                clientX: (t0.clientX + t1.clientX) / 2,
+                clientY: (t0.clientY + t1.clientY) / 2,
+                pageX: (t0.pageX + t1.pageX) / 2,
+                pageY: (t0.pageY + t1.pageY) / 2
             }
         }
 
-        function onPinchPlace ({ newTouch, oldTouch }: { newTouch?: Touch, oldTouch?: Touch }) {
-            dragging = !isPinching()
-            if (dragging) {
-                const firstFinger = oldTouch || newTouch
-                if (firstFinger) onInputDown(firstFinger)
+        function getTouchDistance (ev: TouchEvent) {
+            const dx = ev.touches[0].pageX - ev.touches[1].pageX;
+            const dy = ev.touches[0].pageY - ev.touches[1].pageY;
+            return Math.sqrt(dx * dx + dy * dy);
+        }
+
+        function onTouchStart (ev: TouchEvent) {
+            preventDefault(ev)
+
+            console.log('onTouchStart', ev)
+            if (ev.touches.length === 1) {
+                buttons = MouseButtonsFlag.Primary
+                onPointerDown(ev.touches[0])
+            } else if (ev.touches.length >= 2) {
+                buttons = MouseButtonsFlag.Secondary
+                onPointerDown(getCenterTouch(ev))
+
+                let lastTouchDistance = getTouchDistance(ev)
+                pinch.next({ distance: lastTouchDistance, delta: 0 })
             }
         }
 
-        function onPinchLift ({ removed, otherTouch }: { removed?: Touch, otherTouch?: Touch }) {
-            // if either finger is down, consider it dragging
-            const sum = touchPinch.fingers.reduce((sum, item) => sum + (item ? 1 : 0), 0)
-            dragging = sum >= 1
+        function onTouchEnd (ev: TouchEvent) {
+            preventDefault(ev)
+
+            console.log('onTouchEnd', ev)
+        }
+
+        function onTouchMove (ev: TouchEvent) {
+            preventDefault(ev)
+
+            if (ev.touches.length === 1) {
+                buttons = MouseButtonsFlag.Primary
+                onPointerMove(ev.touches[0])
+            } else if (ev.touches.length >= 2) {
+                buttons = MouseButtonsFlag.Secondary
+                onPointerDown(getCenterTouch(ev))
 
-            if (dragging && otherTouch) {
-                eventOffset(mouseStart, otherTouch, element)
+                const touchDistance = getTouchDistance(ev)
+                pinch.next({ delta: lastTouchDistance - touchDistance, distance: touchDistance })
+                lastTouchDistance = touchDistance
             }
+
+            // if (dragging === DraggingState.Stopped || isPinching()) return
+
+            // // find currently active finger
+            // for (let i = 0; i < ev.changedTouches.length; i++) {
+            //     const changed = ev.changedTouches[i]
+            //     const idx = touchPinch.indexOfTouch(changed)
+            //     if (idx !== -1) {
+            //         onInputMove(changed)
+            //         break
+            //     }
+            // }
         }
 
-        function isPinching () {
-            return touchPinch.pinching
+        // function onPinchPlace ({ newTouch, oldTouch }: { newTouch?: Touch, oldTouch?: Touch }) {
+        //     dragging = isPinching() ? DraggingState.Stopped : DraggingState.Started
+        //     if (dragging === DraggingState.Started) {
+        //         const firstFinger = oldTouch || newTouch
+        //         if (firstFinger) onInputDown(firstFinger)
+        //     }
+        // }
+
+        // function onPinchLift ({ removed, otherTouch }: { removed?: Touch, otherTouch?: Touch }) {
+        //     // if either finger is down, consider it dragging
+        //     const sum = touchPinch.fingers.reduce((sum, item) => sum + (item ? 1 : 0), 0)
+        //     dragging = sum >= 1 ? DraggingState.Moving : DraggingState.Stopped
+
+        //     if (dragging && otherTouch) {
+        //         eventOffset(mouseStart, otherTouch, element)
+        //     }
+        // }
+
+        // function isPinching () {
+        //     return touchPinch.pinching
+        // }
+
+        // function onPinchChange ({ currentDistance, lastDistance }: { currentDistance: number, lastDistance: number }) {
+        //     pinch.next({ delta: currentDistance - lastDistance })
+        // }
+
+        function onMouseDown (ev: MouseEvent) {
+            preventDefault(ev)
+
+            buttons = getButtons(ev)
+            onPointerDown(ev)
         }
 
-        function onPinchChange ({ currentDistance, lastDistance }: { currentDistance: number, lastDistance: number }) {
-            pinch.next(currentDistance - lastDistance)
+        function onMouseMove (ev: MouseEvent) {
+            preventDefault(ev)
+
+            buttons = getButtons(ev)
+            onPointerMove(ev)
         }
 
-        function onInputDown (ev: MouseEvent | Touch) {
+        function onMouseUp (ev: MouseEvent) {
             preventDefault(ev)
-            eventOffset(mouseStart, ev, element)
-            if (insideBounds(mouseStart)) {
-                dragging = true
+
+            buttons = getButtons(ev)
+            onPointerUp(ev)
+        }
+
+        function onPointerDown (ev: PointerEvent) {
+            eventOffset(pointerStart, ev)
+            if (insideBounds(pointerStart)) {
+                dragging = DraggingState.Started
             }
         }
 
-        function onInputUp () {
-            dragging = false
+        function onPointerUp (ev: PointerEvent) {
+            dragging = DraggingState.Stopped
+
+            if (Vec2.distance(pointerEnd, pointerStart) < 4) {
+                eventOffset(pointerEnd, ev)
+
+                const { pageX, pageY } = ev
+                const [ x, y ] = pointerEnd
+
+                console.log('click', { x, y, pageX, pageY, buttons, modifiers })
+                click.next({ x, y, pageX, pageY, buttons, modifiers })
+            }
         }
 
-        function onInputMove (ev: MouseEvent | Touch) {
-            buttons = getButtons(ev)
-            const end = eventOffset(tmp, ev, element)
-            if (pinch && isPinching()) {
-                Vec2.copy(mouseStart, end)
-                return
+        function onPointerMove (ev: PointerEvent) {
+            eventOffset(pointerEnd, ev)
+            // if (pinch && isPinching()) {
+            //     Vec2.copy(pointerStart, pointerEnd)
+            //     return
+            // }
+            if (dragging === DraggingState.Stopped) return
+
+            Vec2.div(pointerDelta, Vec2.sub(pointerDelta, pointerEnd, pointerStart), getClientSize(rectSize))
+
+            const started = dragging === DraggingState.Started
+            const { pageX, pageY } = ev
+            const [ x, y ] = pointerEnd
+            const [ dx, dy ] = pointerDelta
+            // console.log({ x, y, dx, dy, pageX, pageY, buttons, modifiers, started })
+            drag.next({ x, y, dx, dy, pageX, pageY, buttons, modifiers, started })
+
+            Vec2.copy(pointerStart, pointerEnd)
+            dragging = DraggingState.Moving
+        }
+
+        function onMouseWheel(ev: MouseWheelEvent) {
+            if (noScroll) {
+                ev.preventDefault()
+            }
+            const mode = ev.deltaMode
+            let dx = ev.deltaX || 0
+            let dy = ev.deltaY || 0
+            let dz = ev.deltaZ || 0
+            let scale = 1
+            switch (mode) {
+                case 1: scale = lineHeight; break
+                case 2: scale = window.innerHeight; break
+            }
+            scale *= 0.0001
+            dx *= scale
+            dy *= scale
+            dz *= scale
+            if (dx || dy || dz) {
+                wheel.next({ dx, dy, dz, buttons, modifiers })
             }
-            if (!dragging) return
-            const rect = getClientSize(tmp2)
-            const dx = (end[0] - mouseStart[0]) / rect[0]
-            const dy = (end[1] - mouseStart[1]) / rect[1]
-            drag.next({ dx, dy, buttons, modifiers })
-            mouseStart[0] = end[0]
-            mouseStart[1] = end[1]
         }
 
         function insideBounds (pos: Vec2) {
@@ -259,12 +428,17 @@ namespace InputObserver {
         }
 
         function getClientSize (out: Vec2) {
-            let source = element
-            if (source instanceof Window || source instanceof Document || source === document.body) {
-                source = document.documentElement
-            }
-            out[0] = source.clientWidth
-            out[1] = source.clientHeight
+            out[0] = element.clientWidth
+            out[1] = element.clientHeight
+            return out
+        }
+
+        function eventOffset (out: Vec2, ev: PointerEvent) {
+            const cx = ev.clientX || 0
+            const cy = ev.clientY || 0
+            const rect = element.getBoundingClientRect()
+            out[0] = cx - rect.left
+            out[1] = cy - rect.top
             return out
         }
     }

+ 0 - 184
src/mol-util/input/mouse-change.ts

@@ -1,184 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-/*
- * This code has been modified from https://github.com/mikolalysenko/mouse-change,
- * copyright (c) 2015 Mikola Lysenko. MIT License
- */
-
-import { Subject } from 'rxjs';
-
-import * as mouse from './mouse-event'
-
-
-
-interface MouseChange {
-    change: Subject<number>,
-    dispose: () => void
-}
-
-namespace MouseChange {
-    export type Modifiers = {
-        shift: boolean,
-        alt: boolean,
-        control: boolean,
-        meta: boolean
-    }
-    export type Info = {
-        buttons: number,
-        x: number,
-        y: number,
-        modifiers: Modifiers
-    }
-
-    export function create(element: Element) {
-        let buttonState = 0
-        let x = 0
-        let y = 0
-        const mods: Modifiers = {
-            shift: false,
-            alt: false,
-            control: false,
-            meta: false
-        }
-        let attached = false
-
-        const change = new Subject<Info>()
-
-        // Attach listeners
-        attachListeners()
-
-        return {
-            change,
-            dispose
-        }
-
-        function updateMods (event: MouseEvent | KeyboardEvent) {
-            let changed = false
-            if ('altKey' in event) {
-                changed = changed || event.altKey !== mods.alt
-                mods.alt = !!event.altKey
-            }
-            if ('shiftKey' in event) {
-                changed = changed || event.shiftKey !== mods.shift
-                mods.shift = !!event.shiftKey
-            }
-            if ('ctrlKey' in event) {
-                changed = changed || event.ctrlKey !== mods.control
-                mods.control = !!event.ctrlKey
-            }
-            if ('metaKey' in event) {
-                changed = changed || event.metaKey !== mods.meta
-                mods.meta = !!event.metaKey
-            }
-            return changed
-        }
-
-        function handleEvent (nextButtons: number, event: MouseEvent) {
-            const nextX = mouse.x(event)
-            const nextY = mouse.y(event)
-            if ('buttons' in event) {
-                nextButtons = event.buttons | 0
-            }
-            if (nextButtons !== buttonState || nextX !== x || nextY !== y || updateMods(event) ) {
-                buttonState = nextButtons | 0
-                x = nextX || 0
-                y = nextY || 0
-
-                change.next({ buttons: buttonState, x, y, modifiers: mods })
-            }
-        }
-
-        function clearState (event: MouseEvent) {
-            handleEvent(0, event)
-        }
-
-        function handleBlur () {
-            if (buttonState || x || y || mods.shift || mods.alt || mods.meta || mods.control) {
-                x = y = 0
-                buttonState = 0
-                mods.shift = mods.alt = mods.control = mods.meta = false
-                change.next({ buttons: 0, x: 0, y: 0, modifiers: mods })
-            }
-        }
-
-        function handleMods (event: MouseEvent | KeyboardEvent) {
-            if (updateMods(event)) {
-                change.next({ buttons: buttonState, x, y, modifiers: mods })
-            }
-        }
-
-        function handleMouseMove (event: MouseEvent) {
-            if (mouse.buttons(event) === 0) {
-                handleEvent(0, event)
-            } else {
-                handleEvent(buttonState, event)
-            }
-        }
-
-        function handleMouseDown (event: MouseEvent) {
-            handleEvent(buttonState | mouse.buttons(event), event)
-        }
-
-        function handleMouseUp (event: MouseEvent) {
-            handleEvent(buttonState & ~mouse.buttons(event), event)
-        }
-
-        function attachListeners () {
-            if (attached) return
-            attached = true
-
-            element.addEventListener('mousemove', handleMouseMove as EventListener)
-            element.addEventListener('mousedown', handleMouseDown as EventListener)
-            element.addEventListener('mouseup', handleMouseUp as EventListener)
-
-            element.addEventListener('mouseleave', clearState as EventListener)
-            element.addEventListener('mouseenter', clearState as EventListener)
-            element.addEventListener('mouseout', clearState as EventListener)
-            element.addEventListener('mouseover', clearState as EventListener)
-
-            element.addEventListener('blur', handleBlur)
-            element.addEventListener('keyup', handleMods as EventListener)
-            element.addEventListener('keydown', handleMods as EventListener)
-            element.addEventListener('keypress', handleMods as EventListener)
-
-            if (!(element instanceof Window)) {
-                window.addEventListener('blur', handleBlur)
-                window.addEventListener('keyup', handleMods)
-                window.addEventListener('keydown', handleMods)
-                window.addEventListener('keypress', handleMods)
-            }
-        }
-
-        function dispose () {
-            if (!attached) return
-            attached = false
-
-            element.removeEventListener('mousemove', handleMouseMove as EventListener)
-            element.removeEventListener('mousedown', handleMouseDown as EventListener)
-            element.removeEventListener('mouseup', handleMouseUp as EventListener)
-
-            element.removeEventListener('mouseleave', clearState as EventListener)
-            element.removeEventListener('mouseenter', clearState as EventListener)
-            element.removeEventListener('mouseout', clearState as EventListener)
-            element.removeEventListener('mouseover', clearState as EventListener)
-
-            element.removeEventListener('blur', handleBlur)
-            element.removeEventListener('keyup', handleMods as EventListener)
-            element.removeEventListener('keydown', handleMods as EventListener)
-            element.removeEventListener('keypress', handleMods as EventListener)
-
-            if (!(element instanceof Window)) {
-                window.removeEventListener('blur', handleBlur)
-                window.removeEventListener('keyup', handleMods)
-                window.removeEventListener('keydown', handleMods)
-                window.removeEventListener('keypress', handleMods)
-            }
-        }
-    }
-}
-
-export default MouseChange

+ 0 - 65
src/mol-util/input/mouse-event.ts

@@ -1,65 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-/*
- * This code has been modified from https://github.com/mikolalysenko/mouse-event,
- * copyright (c) 2015 Mikola Lysenko. MIT License
- */
-
-export function buttons(event: MouseEvent) {
-    if (typeof event === 'object') {
-        if ('buttons' in event) {
-            return event.buttons
-        } else if ('which' in event) {
-            const b = (event as any).which  // 'any' to support older browsers
-            if (b === 2) {
-                return 4
-            } else if (b === 3) {
-                return 2
-            } else if (b > 0) {
-                return 1<<(b-1)
-            }
-        } else if ('button' in event) {
-            const b = (event as any).button  // 'any' to support older browsers
-            if (b === 1) {
-                return 4
-            } else if (b === 2) {
-                return 2
-            } else if (b >= 0) {
-                return 1<<b
-            }
-        }
-    }
-    return 0
-}
-
-export function element(event: MouseEvent) {
-    return event.target as Element
-}
-
-export function x(event: MouseEvent) {
-    if (typeof event === 'object') {
-        if ('offsetX' in event) {
-            return event.offsetX
-        }
-        const target = element(event)
-        const bounds = target.getBoundingClientRect()
-        return (event as any).clientX - bounds.left  // 'any' to support older browsers
-    }
-    return 0
-}
-
-export function y(event: MouseEvent) {
-    if (typeof event === 'object') {
-        if ('offsetY' in event) {
-            return event.offsetY
-        }
-        const target = element(event)
-        const bounds = target.getBoundingClientRect()
-        return (event as any).clientY - bounds.top  // 'any' to support older browsers
-    }
-    return 0
-}

+ 0 - 67
src/mol-util/input/mouse-wheel.ts

@@ -1,67 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-/*
- * This code has been modified from https://github.com/mikolalysenko/mouse-wheel,
- * copyright (c) 2015 Mikola Lysenko. MIT License
- */
-
-import { Subject } from 'rxjs';
-import toPixels from '../to-pixels'
-
-interface MouseWheel {
-    noScroll: boolean
-    wheel: Subject<{ dx: number, dy: number, dz: number, event: WheelEvent }>
-    dispose: () => void
-}
-
-namespace MouseWheel {
-    export function create(element: Element, noScroll = true): MouseWheel {
-        const lineHeight = toPixels('ex', element)
-        let disposed = false
-        const wheel = new Subject<{ dx: number, dy: number, dz: number, event: WheelEvent }>()
-
-        element.addEventListener('wheel', listener)
-
-        return {
-            get noScroll () { return noScroll },
-            set noScroll (value: boolean) { noScroll = value },
-
-            wheel,
-            dispose
-        }
-
-        function listener(event: MouseWheelEvent) {
-            if (noScroll) {
-                event.preventDefault()
-            }
-            const mode = event.deltaMode
-            let dx = event.deltaX || 0
-            let dy = event.deltaY || 0
-            let dz = event.deltaZ || 0
-            let scale = 1
-            switch (mode) {
-                case 1: scale = lineHeight; break
-                case 2: scale = window.innerHeight; break
-            }
-            dx *= scale
-            dy *= scale
-            dz *= scale
-            if (dx || dy || dz) {
-                wheel.next({ dx, dy, dz, event })
-            }
-        }
-
-        function dispose() {
-            if (disposed) return
-            disposed = true
-            element.removeEventListener('wheel', listener)
-            wheel.unsubscribe()
-        }
-    }
-}
-
-export default MouseWheel

+ 2 - 2
src/mol-util/input/touch-pinch.ts

@@ -147,9 +147,9 @@ namespace TouchPinch {
                         eventOffset(finger.position, movedTouch, target)
                     }
                 }
-                }
+            }
 
-                if (activeCount === 2 && changed) {
+            if (activeCount === 2 && changed) {
                 const currentDistance = computeDistance()
                 change.next({ currentDistance, lastDistance })
                 lastDistance = currentDistance