OV.GetClientCoordinates = function (canvas, clientX, clientY) { if (canvas.getBoundingClientRect) { let clientRect = canvas.getBoundingClientRect (); clientX -= clientRect.left; clientY -= clientRect.top; } if (window.pageXOffset && window.pageYOffset) { clientX += window.pageXOffset; clientY += window.pageYOffset; } return (new OV.Coord2D (clientX, clientY)); }; OV.Camera = class { constructor (eye, center, up) { this.eye = eye; this.center = center; this.up = up; } Clone () { return new OV.Camera ( this.eye.Clone (), this.center.Clone (), this.up.Clone () ); } }; OV.MouseInteraction = class { constructor () { this.prev = new OV.Coord2D (0.0, 0.0); this.curr = new OV.Coord2D (0.0, 0,0); this.diff = new OV.Coord2D (0.0, 0.0); this.buttons = []; } Down (canvas, ev) { this.buttons.push (ev.which); this.curr = this.GetCurrent (canvas, ev); this.prev = this.curr.Clone (); } Move (canvas, ev) { this.curr = this.GetCurrent (canvas, ev); this.diff = OV.SubCoord2D (this.curr, this.prev); this.prev = this.curr.Clone (); } Up (canvas, ev) { let buttonIndex = this.buttons.indexOf (ev.which); if (buttonIndex !== -1) { this.buttons.splice (buttonIndex, 1); } this.curr = this.GetCurrent (canvas, ev); } Leave (canvas, ev) { this.buttons = []; this.curr = this.GetCurrent (canvas, ev); } IsButtonDown () { return this.buttons.length > 0; } GetButton () { let length = this.buttons.length; if (length === 0) { return 0; } return this.buttons[length - 1]; } GetMoveDiff () { return this.diff; } GetCurrent (canvas, ev) { return OV.GetClientCoordinates (canvas, ev.clientX, ev.clientY); } }; OV.TouchInteraction = class { constructor () { this.prevPos = new OV.Coord2D (0.0, 0.0); this.currPos = new OV.Coord2D (0.0, 0,0); this.diffPos = new OV.Coord2D (0.0, 0.0); this.prevDist = 0.0; this.currDist = 0.0; this.diffDist = 0.0; this.fingers = 0; } Start (canvas, ev) { if (ev.touches.length === 0) { return; } this.fingers = ev.touches.length; this.currPos = this.GetCurrent (canvas, ev); this.prevPos = this.currPos.Clone (); this.currDist = this.GetTouchDistance (canvas, ev); this.prevDist = this.currDist; } Move (canvas, ev) { if (ev.touches.length === 0) { return; } this.currPos = this.GetCurrent (canvas, ev); this.diffPos = OV.SubCoord2D (this.currPos, this.prevPos); this.prevPos = this.currPos.Clone (); this.currDist = this.GetTouchDistance (canvas, ev); this.diffDist = this.currDist - this.prevDist; this.prevDist = this.currDist; } End (canvas, ev) { if (ev.touches.length === 0) { return; } this.fingers = 0; this.currPos = this.GetCurrent (canvas, ev); this.currDist = this.GetTouchDistance (canvas, ev); } IsFingerDown () { return this.fingers !== 0; } GetFingerCount () { return this.fingers; } GetMoveDiff () { return this.diffPos; } GetDistanceDiff () { return this.diffDist; } GetCurrent (canvas, ev) { let coord = null; if (ev.touches.length !== 0) { let touchEv = ev.touches[0]; coord = OV.GetClientCoordinates (canvas, touchEv.pageX, touchEv.pageY); } return coord; } GetTouchDistance (canvas, ev) { if (ev.touches.length !== 2) { return 0.0; } let touchEv1 = ev.touches[0]; let touchEv2 = ev.touches[1]; let distance = OV.CoordDistance2D ( OV.GetClientCoordinates (canvas, touchEv1.pageX, touchEv1.pageY), OV.GetClientCoordinates (canvas, touchEv2.pageX, touchEv2.pageY) ); return distance; } }; OV.ClickDetector = class { constructor () { this.isClick = false; this.button = 0; } Down (ev) { this.isClick = true; this.button = ev.which; } Move () { this.isClick = false; } Up (ev) { if (this.isClick && ev.which !== this.button) { this.isClick = false; } } Leave (ev) { this.isClick = false; this.button = 0; } IsClick () { return this.isClick; } }; OV.Navigation = class { constructor (canvas, camera) { this.canvas = canvas; this.camera = camera; this.orbitCenter = this.camera.center.Clone (); this.fixUpVector = true; this.mouse = new OV.MouseInteraction (); this.touch = new OV.TouchInteraction (); this.clickDetector = new OV.ClickDetector (); this.onUpdate = null; this.onClick = null; if (this.canvas.addEventListener) { this.canvas.addEventListener ('mousedown', this.OnMouseDown.bind (this)); this.canvas.addEventListener ('wheel', this.OnMouseWheel.bind (this)); this.canvas.addEventListener ('touchstart', this.OnTouchStart.bind (this)); this.canvas.addEventListener ('touchmove', this.OnTouchMove.bind (this)); this.canvas.addEventListener ('touchend', this.OnTouchEnd.bind (this)); this.canvas.addEventListener ('contextmenu', this.OnContextMenu.bind (this)); } if (document.addEventListener) { document.addEventListener ('mousemove', this.OnMouseMove.bind (this)); document.addEventListener ('mouseup', this.OnMouseUp.bind (this)); document.addEventListener ('mouseleave', this.OnMouseLeave.bind (this)); } } SetUpdateHandler (onUpdate) { this.onUpdate = onUpdate; } SetClickHandler (onClick) { this.onClick = onClick; } IsFixUpVector () { return this.fixUpVector; } SetFixUpVector (fixUpVector) { this.fixUpVector = fixUpVector; } GetCamera () { return this.camera; } SetCamera (camera) { this.camera = camera; } MoveCamera (newCamera, stepCount) { function Step (obj, steps, count, index) { obj.camera.eye = steps.eye[index]; obj.camera.center = steps.center[index]; obj.camera.up = steps.up[index]; obj.Update (); if (index < count - 1) { requestAnimationFrame (function () { Step (obj, steps, count, index + 1); }); } } if (newCamera === null) { return; } if (stepCount === 0) { this.SetCamera (newCamera); return; } if (OV.CoordIsEqual3D (this.camera.eye, newCamera.eye) && OV.CoordIsEqual3D (this.camera.center, newCamera.center) && OV.CoordIsEqual3D (this.camera.up, newCamera.up)) { return; } let tweenFunc = OV.ParabolicTweenFunction; let obj = this; let steps = { eye : OV.TweenCoord3D (this.camera.eye, newCamera.eye, stepCount, tweenFunc), center : OV.TweenCoord3D (this.camera.center, newCamera.center, stepCount, tweenFunc), up : OV.TweenCoord3D (this.camera.up, newCamera.up, stepCount, tweenFunc) }; requestAnimationFrame (function () { Step (obj, steps, stepCount, 0); }); this.Update (); } FitToSphere (center, radius, fov) { if (OV.IsZero (radius)) { return; } let fitCamera = this.GetFitToSphereCamera (center, radius, fov); this.camera = fitCamera; this.orbitCenter = this.camera.center.Clone (); this.Update (); } GetFitToSphereCamera (center, radius, fov) { if (OV.IsZero (radius)) { return null; } let fitCamera = this.camera.Clone (); let offsetToOrigo = OV.SubCoord3D (fitCamera.center, center); fitCamera.eye = OV.SubCoord3D (fitCamera.eye, offsetToOrigo); fitCamera.center = center.Clone (); let centerEyeDirection = OV.SubCoord3D (fitCamera.eye, fitCamera.center).Normalize (); let fieldOfView = fov / 2.0; if (this.canvas.width < this.canvas.height) { fieldOfView = fieldOfView * this.canvas.width / this.canvas.height; } let distance = radius / Math.sin (fieldOfView * OV.DegRad); fitCamera.eye = fitCamera.center.Clone ().Offset (centerEyeDirection, distance); this.orbitCenter = fitCamera.center.Clone (); return fitCamera; } OnMouseDown (ev) { ev.preventDefault (); this.mouse.Down (this.canvas, ev); this.clickDetector.Down (ev); } OnMouseMove (ev) { this.mouse.Move (this.canvas, ev); this.clickDetector.Move (); if (!this.mouse.IsButtonDown ()) { return; } let moveDiff = this.mouse.GetMoveDiff (); let mouseButton = this.mouse.GetButton (); if (mouseButton === 1) { let orbitRatio = 0.5; this.Orbit (moveDiff.x * orbitRatio, moveDiff.y * orbitRatio); } else if (mouseButton === 2 || mouseButton === 3) { let eyeCenterDistance = OV.CoordDistance3D (this.camera.eye, this.camera.center); let panRatio = 0.001 * eyeCenterDistance; this.Pan (moveDiff.x * panRatio, moveDiff.y * panRatio); } this.Update (); } OnMouseUp (ev) { this.mouse.Up (this.canvas, ev); this.clickDetector.Up (ev); if (this.clickDetector.IsClick ()) { let isCtrlPressed = (ev.ctrlKey || ev.metaKey); this.Click (ev.which, isCtrlPressed, ev.clientX, ev.clientY); } } OnMouseLeave (ev) { this.mouse.Leave (this.canvas, ev); this.clickDetector.Leave (ev); } OnTouchStart (ev) { ev.preventDefault (); this.touch.Start (this.canvas, ev); } OnTouchMove (ev) { ev.preventDefault (); this.touch.Move (this.canvas, ev); if (!this.touch.IsFingerDown ()) { return; } let moveDiff = this.touch.GetMoveDiff (); let distanceDiff = this.touch.GetDistanceDiff (); let fingerCount = this.touch.GetFingerCount (); if (fingerCount === 1) { let orbitRatio = 0.5; this.Orbit (moveDiff.x * orbitRatio, moveDiff.y * orbitRatio); } else if (fingerCount === 2) { let zoomRatio = 0.005; this.Zoom (distanceDiff * zoomRatio); let panRatio = 0.001 * OV.CoordDistance3D (this.camera.eye, this.camera.center); this.Pan (moveDiff.x * panRatio, moveDiff.y * panRatio); } this.Update (); } OnTouchEnd (ev) { ev.preventDefault (); this.touch.End (this.canvas, ev); } OnMouseWheel (ev) { ev.preventDefault (); let params = ev || window.event; let delta = -params.deltaY / 40; let ratio = 0.1; if (delta < 0) { ratio = ratio * -1.0; } this.Zoom (ratio); this.Update (); } OnContextMenu (ev) { ev.preventDefault (); } Orbit (angleX, angleY) { let radAngleX = angleX * OV.DegRad; let radAngleY = angleY * OV.DegRad; let viewDirection = OV.SubCoord3D (this.camera.center, this.camera.eye).Normalize (); let horizontalDirection = OV.CrossVector3D (viewDirection, this.camera.up).Normalize (); let differentCenter = !OV.CoordIsEqual3D (this.orbitCenter, this.camera.center); if (this.fixUpVector) { let originalAngle = OV.VectorAngle3D (viewDirection, this.camera.up); let newAngle = originalAngle + radAngleY; if (OV.IsGreater (newAngle, 0.0) && OV.IsLower (newAngle, Math.PI)) { this.camera.eye.Rotate (horizontalDirection, -radAngleY, this.orbitCenter); if (differentCenter) { this.camera.center.Rotate (horizontalDirection, -radAngleY, this.orbitCenter); } } this.camera.eye.Rotate (this.camera.up, -radAngleX, this.orbitCenter); if (differentCenter) { this.camera.center.Rotate (this.camera.up, -radAngleX, this.orbitCenter); } } else { let verticalDirection = OV.CrossVector3D (horizontalDirection, viewDirection).Normalize (); this.camera.eye.Rotate (horizontalDirection, -radAngleY, this.orbitCenter); this.camera.eye.Rotate (verticalDirection, -radAngleX, this.orbitCenter); if (differentCenter) { this.camera.center.Rotate (horizontalDirection, -radAngleY, this.orbitCenter); this.camera.center.Rotate (verticalDirection, -radAngleX, this.orbitCenter); } this.camera.up = verticalDirection; } } Pan (moveX, moveY) { let viewDirection = OV.SubCoord3D (this.camera.center, this.camera.eye).Normalize (); let horizontalDirection = OV.CrossVector3D (viewDirection, this.camera.up).Normalize (); let verticalDirection = OV.CrossVector3D (horizontalDirection, viewDirection).Normalize (); this.camera.eye.Offset (horizontalDirection, -moveX); this.camera.center.Offset (horizontalDirection, -moveX); this.camera.eye.Offset (verticalDirection, moveY); this.camera.center.Offset (verticalDirection, moveY); } Zoom (ratio) { let direction = OV.SubCoord3D (this.camera.center, this.camera.eye); let distance = direction.Length (); let move = distance * ratio; this.camera.eye.Offset (direction, move); } Update () { if (this.onUpdate) { this.onUpdate (); } } Click (button, isCtrlPressed, clientX, clientY) { if (this.onClick) { let mouseCoords = OV.GetClientCoordinates (this.canvas, clientX, clientY); this.onClick (button, isCtrlPressed, mouseCoords); } } };