Parcourir la source

wip, seperating inputs from controls

Alexander Rose il y a 7 ans
Parent
commit
d4670e0e7d

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

@@ -20,6 +20,10 @@ export namespace Viewport {
     export function clone(viewport: Viewport): Viewport {
     export function clone(viewport: Viewport): Viewport {
         return { ...viewport }
         return { ...viewport }
     }
     }
+
+    export function copy(target: Viewport, source: Viewport): Viewport {
+        return Object.assign(target, source)
+    }
 }
 }
 
 
 const tmpVec3 = Vec3.zero()
 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
  * copyright (c) 2010-2018 three.js authors. MIT License
  */
  */
 
 
-import { Subject } from 'rxjs';
-
 import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra';
 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 = {
 export const DefaultTrackballControlsProps = {
+    noScroll: true,
+
     rotateSpeed: 3.0,
     rotateSpeed: 3.0,
-    zoomSpeed: 2.2,
+    zoomSpeed: 2.0,
     panSpeed: 0.1,
     panSpeed: 0.1,
 
 
     staticMoving: false,
     staticMoving: false,
@@ -29,32 +28,14 @@ export const DefaultTrackballControlsProps = {
 }
 }
 export type TrackballControlsProps = Partial<typeof 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 {
 interface Object {
     position: Vec3,
     position: Vec3,
     direction: Vec3,
     direction: Vec3,
     up: Vec3,
     up: Vec3,
 }
 }
 
 
-interface Screen {
-    left: number
-    top: number
-    width: number
-    height: number
-}
-
 interface TrackballControls {
 interface TrackballControls {
-    change: Subject<void>
-    start: Subject<void>
-    end: Subject<void>
+    viewport: Viewport
 
 
     dynamicDampingFactor: number
     dynamicDampingFactor: number
     rotateSpeed: number
     rotateSpeed: number
@@ -62,7 +43,6 @@ interface TrackballControls {
     panSpeed: number
     panSpeed: number
 
 
     update: () => void
     update: () => void
-    handleResize: () => void
     reset: () => void
     reset: () => void
     dispose: () => void
     dispose: () => void
 }
 }
@@ -71,23 +51,23 @@ namespace TrackballControls {
     export function create (element: Element, object: Object, props: TrackballControlsProps = {}): TrackballControls {
     export function create (element: Element, object: Object, props: TrackballControlsProps = {}): TrackballControls {
         const p = { ...DefaultTrackballControlsProps, ...props }
         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 { rotateSpeed, zoomSpeed, panSpeed } = p
         let { staticMoving, dynamicDampingFactor } = p
         let { staticMoving, dynamicDampingFactor } = p
         let { minDistance, maxDistance } = 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
         // internals
         const target = Vec3.zero()
         const target = Vec3.zero()
         const lastPosition = Vec3.zero()
         const lastPosition = Vec3.zero()
 
 
-        let _state = STATE.NONE
-        let _prevState = STATE.NONE
-
         const _eye = Vec3.zero()
         const _eye = Vec3.zero()
 
 
         const _movePrev = Vec2.zero()
         const _movePrev = Vec2.zero()
@@ -99,8 +79,8 @@ namespace TrackballControls {
         const _zoomStart = Vec2.zero()
         const _zoomStart = Vec2.zero()
         const _zoomEnd = Vec2.zero()
         const _zoomEnd = Vec2.zero()
 
 
-        let _touchZoomDistanceStart = 0
-        let _touchZoomDistanceEnd = 0
+        // let _touchZoomDistanceStart = 0
+        // let _touchZoomDistanceEnd = 0
 
 
         const _panStart = Vec2.zero()
         const _panStart = Vec2.zero()
         const _panEnd = Vec2.zero()
         const _panEnd = Vec2.zero()
@@ -110,30 +90,12 @@ namespace TrackballControls {
         const position0 = Vec3.clone(object.position)
         const position0 = Vec3.clone(object.position)
         const up0 = Vec3.clone(object.up)
         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()
         const mouseOnScreenVec2 = Vec2.zero()
         function getMouseOnScreen(pageX: number, pageY: number) {
         function getMouseOnScreen(pageX: number, pageY: number) {
             Vec2.set(
             Vec2.set(
                 mouseOnScreenVec2,
                 mouseOnScreenVec2,
-                (pageX - screen.left) / screen.width,
-                (pageY - screen.top) / screen.height
+                (pageX - viewport.x) / viewport.width,
+                (pageY - viewport.y) / viewport.height
             );
             );
             return mouseOnScreenVec2;
             return mouseOnScreenVec2;
         }
         }
@@ -142,8 +104,8 @@ namespace TrackballControls {
         function getMouseOnCircle(pageX: number, pageY: number) {
         function getMouseOnCircle(pageX: number, pageY: number) {
             Vec2.set(
             Vec2.set(
                 mouseOnCircleVec2,
                 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;
             return mouseOnCircleVec2;
         }
         }
@@ -196,21 +158,32 @@ namespace TrackballControls {
 
 
 
 
         function zoomCamera () {
         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)
                 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)
             cameraLookAt(object.position, object.up, object.direction, target)
 
 
             if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
             if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
-                change.next()
                 Vec3.copy(lastPosition, object.position)
                 Vec3.copy(lastPosition, object.position)
             }
             }
         }
         }
 
 
         function reset() {
         function reset() {
-            _state = STATE.NONE;
-            _prevState = STATE.NONE;
-
             Vec3.copy(target, target0)
             Vec3.copy(target, target0)
             Vec3.copy(object.position, position0)
             Vec3.copy(object.position, position0)
             Vec3.copy(object.up, up0)
             Vec3.copy(object.up, up0)
 
 
             Vec3.sub(_eye, object.position, target)
             Vec3.sub(_eye, object.position, target)
-
             cameraLookAt(object.position, object.up, object.direction, target)
             cameraLookAt(object.position, object.up, object.direction, target)
-
-            change.next()
             Vec3.copy(lastPosition, object.position)
             Vec3.copy(lastPosition, object.position)
         }
         }
 
 
         // listeners
         // 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)
                     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)
                     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
         // force an update at start
         update();
         update();
 
 
         return {
         return {
-            change,
-            start,
-            end,
+            viewport,
 
 
             get dynamicDampingFactor() { return dynamicDampingFactor },
             get dynamicDampingFactor() { return dynamicDampingFactor },
             set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value },
             set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value },
@@ -473,7 +379,6 @@ namespace TrackballControls {
             set panSpeed(value: number ) { panSpeed = value },
             set panSpeed(value: number ) { panSpeed = value },
 
 
             update,
             update,
-            handleResize,
             reset,
             reset,
             dispose
             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 { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { ValueCell } from 'mol-util';
 import { ValueCell } from 'mol-util';
 import TrackballControls from './controls/trackball';
 import TrackballControls from './controls/trackball';
-import { element } from 'mol-util/input/mouse-event';
+import { Viewport } from './camera/util';
 
 
 let _renderObjectId = 0;
 let _renderObjectId = 0;
 function getNextId() {
 function getNextId() {
@@ -55,6 +55,7 @@ interface Renderer {
     clear: () => void
     clear: () => void
     draw: () => void
     draw: () => void
     frame: () => void
     frame: () => void
+    handleResize: () => void
 }
 }
 
 
 function resizeCanvas (canvas: HTMLCanvasElement, element: HTMLElement) {
 function resizeCanvas (canvas: HTMLCanvasElement, element: HTMLElement) {
@@ -102,10 +103,7 @@ namespace Renderer {
         const objectIdRenderableMap: { [k: number]: Renderable } = {}
         const objectIdRenderableMap: { [k: number]: Renderable } = {}
 
 
         const extensions = [
         const extensions = [
-            'OES_texture_float',
-            'OES_texture_float_linear',
             'OES_element_index_uint',
             'OES_element_index_uint',
-            'EXT_blend_minmax',
             'ANGLE_instanced_arrays'
             'ANGLE_instanced_arrays'
         ]
         ]
         const optionalExtensions = [
         const optionalExtensions = [
@@ -116,26 +114,20 @@ namespace Renderer {
 
 
         const camera = PerspectiveCamera.create({
         const camera = PerspectiveCamera.create({
             near: 0.01,
             near: 0.01,
-            far: 1000,
+            far: 10000,
             position: Vec3.create(0, 0, 50)
             position: Vec3.create(0, 0, 50)
         })
         })
 
 
         const controls = TrackballControls.create(canvas, camera, {
         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({
         const baseContext = regl({
-            viewport: { x: 0, y: 0, width: canvas.width, height: canvas.height },
             context: {
             context: {
                 model: Mat4.identity(),
                 model: Mat4.identity(),
                 transform: Mat4.identity(),
                 transform: Mat4.identity(),
                 view: camera.view,
                 view: camera.view,
-                projection: camera.projection
+                projection: camera.projection,
             },
             },
             uniforms: {
             uniforms: {
                 model: regl.context('model' as any),
                 model: regl.context('model' as any),
@@ -168,6 +160,9 @@ namespace Renderer {
             })
             })
         }
         }
 
 
+        window.addEventListener('resize', handleResize, false)
+        handleResize()
+
         // TODO animate, draw, requestDraw
         // TODO animate, draw, requestDraw
         return {
         return {
             camera,
             camera,
@@ -200,7 +195,15 @@ namespace Renderer {
             draw,
             draw,
             frame: () => {
             frame: () => {
                 regl.frame((ctx) => draw())
                 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;
         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) {
     export function scale(out: Vec2, a: Vec2, b: number) {
         out[0] = a[0] * b;
         out[0] = a[0] * b;
         out[1] = a[1] * b;
         out[1] = a[1] * b;

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

@@ -94,6 +94,20 @@ namespace Vec3 {
         return out;
         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) {
     export function scale(out: Vec3, a: Vec3, b: number) {
         out[0] = a[0] * b;
         out[0] = a[0] * b;
         out[1] = a[1] * 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) {
 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
         return rootPosition
     } else {
     } else {
         return (element as Element).getBoundingClientRect()
         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 { Vec2 } from 'mol-math/linear-algebra';
 
 
-import MouseWheel from './mouse-wheel'
+import toPixels from '../to-pixels'
 import TouchPinch from './touch-pinch'
 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 (typeof event === 'object') {
         if ('buttons' in event) {
         if ('buttons' in event) {
             return event.buttons
             return event.buttons
@@ -40,133 +39,193 @@ export function getButtons(event: MouseEvent | Touch) {
 }
 }
 
 
 export const DefaultInputObserverProps = {
 export const DefaultInputObserverProps = {
-    parent: window as Window | Element,
-    noScroll: true
+    noScroll: true,
+    noContextMenu: true
 }
 }
 export type InputObserverProps = Partial<typeof DefaultInputObserverProps>
 export type InputObserverProps = Partial<typeof DefaultInputObserverProps>
 
 
-export type MouseModifiers = {
+export type ModifiersKeys = {
     shift: boolean,
     shift: boolean,
     alt: boolean,
     alt: boolean,
     control: boolean,
     control: boolean,
     meta: 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 {
 interface InputObserver {
     noScroll: boolean
     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
     dispose: () => void
 }
 }
 
 
 namespace InputObserver {
 namespace InputObserver {
     export function create (element: Element, props: InputObserverProps = {}): 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,
             shift: false,
             alt: false,
             alt: false,
             control: false,
             control: false,
             meta: 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 disposed = false
         let buttons = 0
         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()
         attach()
 
 
         return {
         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,
             drag,
             wheel,
             wheel,
             pinch,
             pinch,
+            click,
 
 
             dispose
             dispose
         }
         }
 
 
         function attach () {
         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,
             // 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('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('blur', handleBlur)
             element.addEventListener('keyup', handleMods as EventListener)
             element.addEventListener('keyup', handleMods as EventListener)
             element.addEventListener('keydown', handleMods as EventListener)
             element.addEventListener('keydown', handleMods as EventListener)
             element.addEventListener('keypress', 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 () {
         function dispose () {
             if (disposed) return
             if (disposed) return
             disposed = true
             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('blur', handleBlur)
             element.removeEventListener('keyup', handleMods as EventListener)
             element.removeEventListener('keyup', handleMods as EventListener)
             element.removeEventListener('keydown', handleMods as EventListener)
             element.removeEventListener('keydown', handleMods as EventListener)
             element.removeEventListener('keypress', 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) {
         function preventDefault (ev: Event | Touch) {
             if ('preventDefault' in ev) ev.preventDefault()
             if ('preventDefault' in ev) ev.preventDefault()
         }
         }
 
 
+        function onContextMenu(event: Event) {
+            if (noContextMenu) {
+                event.preventDefault()
+            }
+        }
+
         function handleBlur () {
         function handleBlur () {
             if (buttons || modifiers.shift || modifiers.alt || modifiers.meta || modifiers.control) {
             if (buttons || modifiers.shift || modifiers.alt || modifiers.meta || modifiers.control) {
                 buttons = 0
                 buttons = 0
@@ -181,72 +240,182 @@ namespace InputObserver {
             if ('metaKey' in event) modifiers.meta = !!event.metaKey
             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)
             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) {
         function insideBounds (pos: Vec2) {
@@ -259,12 +428,17 @@ namespace InputObserver {
         }
         }
 
 
         function getClientSize (out: Vec2) {
         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
             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)
                         eventOffset(finger.position, movedTouch, target)
                     }
                     }
                 }
                 }
-                }
+            }
 
 
-                if (activeCount === 2 && changed) {
+            if (activeCount === 2 && changed) {
                 const currentDistance = computeDistance()
                 const currentDistance = computeDistance()
                 change.next({ currentDistance, lastDistance })
                 change.next({ currentDistance, lastDistance })
                 lastDistance = currentDistance
                 lastDistance = currentDistance