diff --git a/package-lock.json b/package-lock.json index 016f20fd9a1..31b94d8c362 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,15 +15,14 @@ } }, "3d-view-controls": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/3d-view-controls/-/3d-view-controls-2.2.2.tgz", - "integrity": "sha512-WL0u3PN41lEx/4qvKqV6bJlweUYoW18FXMshW/qHb41AVdZxDReLoJNGYsI7x6jf9bYelEF62BJPQmO7yEnG2w==", + "version": "git://github.com/archmoj/3d-view-controls.git#fe3c77b807618c0d51f4c50a691e2af0486bfda9", + "from": "git://github.com/archmoj/3d-view-controls.git#fe3c77b807618c0d51f4c50a691e2af0486bfda9", "requires": { "3d-view": "^2.0.0", "has-passive-events": "^1.0.0", - "mouse-change": "^1.1.1", + "mouse-change": "^1.4.0", "mouse-event-offset": "^3.0.2", - "mouse-wheel": "^1.0.2", + "mouse-wheel": "^1.2.0", "right-now": "^1.0.0" } }, @@ -4767,9 +4766,8 @@ } }, "gl-axes3d": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/gl-axes3d/-/gl-axes3d-1.4.4.tgz", - "integrity": "sha512-yvM1ZObNa4x7Lhh14sy9PGqNtwkFCX5dTvtkzVXB5qtdm4AWNBOMUiDlYbxn0uwyVXKdoDFxkPtnoTh1eFY+JQ==", + "version": "git://github.com/gl-vis/gl-axes3d.git#7a57149812d2a0bae4283ca927d0df3723af688e", + "from": "git://github.com/gl-vis/gl-axes3d.git#7a57149812d2a0bae4283ca927d0df3723af688e", "requires": { "bit-twiddle": "^1.0.2", "dup": "^1.0.0", @@ -4979,13 +4977,12 @@ } }, "gl-plot3d": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-1.6.3.tgz", - "integrity": "sha512-/pQAJHDrYgbwTYygXn1aErwRX6KDb2WwJAFaRBjr4xOoSC/flC5VI/1LyQFuQwNlxExx58gAm88aMkWwi8sdiQ==", + "version": "git://github.com/gl-vis/gl-plot3d.git#b040fcb93647eeb7f08b0ec631bccbf1cf836620", + "from": "git://github.com/gl-vis/gl-plot3d.git#b040fcb93647eeb7f08b0ec631bccbf1cf836620", "requires": { - "3d-view-controls": "^2.2.2", + "3d-view-controls": "git://github.com/archmoj/3d-view-controls.git#fe3c77b807618c0d51f4c50a691e2af0486bfda9", "a-big-triangle": "^1.0.3", - "gl-axes3d": "^1.4.4", + "gl-axes3d": "git://github.com/gl-vis/gl-axes3d.git#7a57149812d2a0bae4283ca927d0df3723af688e", "gl-fbo": "^2.0.5", "gl-mat4": "^1.2.0", "gl-select-static": "^2.0.4", diff --git a/package.json b/package.json index b3934e0ab2e..e71001c4a5e 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ ] }, "dependencies": { - "3d-view": "^2.0.0", + "3d-view-controls": "git://github.com/archmoj/3d-view-controls.git#fe3c77b807618c0d51f4c50a691e2af0486bfda9", "@plotly/d3-sankey": "^0.5.1", "alpha-shape": "^1.0.0", "array-range": "^1.0.1", @@ -79,7 +79,7 @@ "gl-mat4": "^1.2.0", "gl-mesh3d": "^2.0.7", "gl-plot2d": "^1.4.2", - "gl-plot3d": "^1.6.3", + "gl-plot3d": "git://github.com/gl-vis/gl-plot3d.git#b040fcb93647eeb7f08b0ec631bccbf1cf836620", "gl-pointcloud2d": "^1.0.2", "gl-scatter3d": "^1.1.6", "gl-select-box": "^1.0.3", @@ -91,7 +91,6 @@ "has-hover": "^1.0.1", "has-passive-events": "^1.0.0", "mapbox-gl": "0.45.0", - "matrix-camera-controller": "^2.1.3", "mouse-change": "^1.4.0", "mouse-event-offset": "^3.0.2", "mouse-wheel": "^1.0.2", @@ -105,7 +104,6 @@ "regl-line2d": "3.0.13", "regl-scatter2d": "^3.1.3", "regl-splom": "^1.0.6", - "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^2.0.0", "strongly-connected-components": "^1.0.1", diff --git a/src/plots/gl3d/camera.js b/src/plots/gl3d/camera.js deleted file mode 100644 index 32e796a8760..00000000000 --- a/src/plots/gl3d/camera.js +++ /dev/null @@ -1,277 +0,0 @@ -/** -* Copyright 2012-2019, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - -'use strict'; - -module.exports = createCamera; - -var now = require('right-now'); -var createView = require('3d-view'); -var mouseChange = require('mouse-change'); -var mouseWheel = require('mouse-wheel'); -var mouseOffset = require('mouse-event-offset'); -var supportsPassive = require('has-passive-events'); - -function createCamera(element, options) { - element = element || document.body; - options = options || {}; - - var limits = [ 0.01, Infinity ]; - if('distanceLimits' in options) { - limits[0] = options.distanceLimits[0]; - limits[1] = options.distanceLimits[1]; - } - if('zoomMin' in options) { - limits[0] = options.zoomMin; - } - if('zoomMax' in options) { - limits[1] = options.zoomMax; - } - - var view = createView({ - center: options.center || [0, 0, 0], - up: options.up || [0, 1, 0], - eye: options.eye || [0, 0, 10], - mode: options.mode || 'orbit', - distanceLimits: limits - }); - - var pmatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - var distance = 0.0; - var width = element.clientWidth; - var height = element.clientHeight; - - var camera = { - keyBindingMode: 'rotate', - enableWheel: true, - view: view, - element: element, - delay: options.delay || 16, - rotateSpeed: options.rotateSpeed || 1, - zoomSpeed: options.zoomSpeed || 1, - translateSpeed: options.translateSpeed || 1, - flipX: !!options.flipX, - flipY: !!options.flipY, - modes: view.modes, - tick: function() { - var t = now(); - var delay = this.delay; - var ctime = t - 2 * delay; - view.idle(t - delay); - view.recalcMatrix(ctime); - view.flush(t - (100 + delay * 2)); - var allEqual = true; - var matrix = view.computedMatrix; - for(var i = 0; i < 16; ++i) { - allEqual = allEqual && (pmatrix[i] === matrix[i]); - pmatrix[i] = matrix[i]; - } - var sizeChanged = - element.clientWidth === width && - element.clientHeight === height; - width = element.clientWidth; - height = element.clientHeight; - if(allEqual) return !sizeChanged; - distance = Math.exp(view.computedRadius[0]); - return true; - }, - lookAt: function(center, eye, up) { - view.lookAt(view.lastT(), center, eye, up); - }, - rotate: function(pitch, yaw, roll) { - view.rotate(view.lastT(), pitch, yaw, roll); - }, - pan: function(dx, dy, dz) { - view.pan(view.lastT(), dx, dy, dz); - }, - translate: function(dx, dy, dz) { - view.translate(view.lastT(), dx, dy, dz); - } - }; - - Object.defineProperties(camera, { - matrix: { - get: function() { - return view.computedMatrix; - }, - set: function(mat) { - view.setMatrix(view.lastT(), mat); - return view.computedMatrix; - }, - enumerable: true - }, - mode: { - get: function() { - return view.getMode(); - }, - set: function(mode) { - var curUp = view.computedUp.slice(); - var curEye = view.computedEye.slice(); - var curCenter = view.computedCenter.slice(); - view.setMode(mode); - if(mode === 'turntable') { - // Hacky time warping stuff to generate smooth animation - var t0 = now(); - view._active.lookAt(t0, curEye, curCenter, curUp); - view._active.lookAt(t0 + 500, curEye, curCenter, [0, 0, 1]); - view._active.flush(t0); - } - return view.getMode(); - }, - enumerable: true - }, - center: { - get: function() { - return view.computedCenter; - }, - set: function(ncenter) { - view.lookAt(view.lastT(), null, ncenter); - return view.computedCenter; - }, - enumerable: true - }, - eye: { - get: function() { - return view.computedEye; - }, - set: function(neye) { - view.lookAt(view.lastT(), neye); - return view.computedEye; - }, - enumerable: true - }, - up: { - get: function() { - return view.computedUp; - }, - set: function(nup) { - view.lookAt(view.lastT(), null, null, nup); - return view.computedUp; - }, - enumerable: true - }, - distance: { - get: function() { - return distance; - }, - set: function(d) { - view.setDistance(view.lastT(), d); - return d; - }, - enumerable: true - }, - distanceLimits: { - get: function() { - return view.getDistanceLimits(limits); - }, - set: function(v) { - view.setDistanceLimits(v); - return v; - }, - enumerable: true - } - }); - - element.addEventListener('contextmenu', function(ev) { - ev.preventDefault(); - return false; - }); - - var lastX = 0; - var lastY = 0; - var lastMods = {shift: false, control: false, alt: false, meta: false}; - camera.mouseListener = mouseChange(element, handleInteraction); - - // enable simple touch interactions - element.addEventListener('touchstart', function(ev) { - var xy = mouseOffset(ev.changedTouches[0], element); - handleInteraction(0, xy[0], xy[1], lastMods); - handleInteraction(1, xy[0], xy[1], lastMods); - - ev.preventDefault(); - }, supportsPassive ? {passive: false} : false); - element.addEventListener('touchmove', function(ev) { - var xy = mouseOffset(ev.changedTouches[0], element); - handleInteraction(1, xy[0], xy[1], lastMods); - - ev.preventDefault(); - }, supportsPassive ? {passive: false} : false); - element.addEventListener('touchend', function(ev) { - handleInteraction(0, lastX, lastY, lastMods); - - ev.preventDefault(); - }, supportsPassive ? {passive: false} : false); - - function handleInteraction(buttons, x, y, mods) { - var keyBindingMode = camera.keyBindingMode; - - if(keyBindingMode === false) return; - - var rotate = keyBindingMode === 'rotate'; - var pan = keyBindingMode === 'pan'; - var zoom = keyBindingMode === 'zoom'; - - var ctrl = !!mods.control; - var alt = !!mods.alt; - var shift = !!mods.shift; - var left = !!(buttons & 1); - var right = !!(buttons & 2); - var middle = !!(buttons & 4); - - var scale = 1.0 / element.clientHeight; - var dx = scale * (x - lastX); - var dy = scale * (y - lastY); - - var flipX = camera.flipX ? 1 : -1; - var flipY = camera.flipY ? 1 : -1; - - var t = now(); - - var drot = Math.PI * camera.rotateSpeed; - - if((rotate && left && !ctrl && !alt && !shift) || (left && !ctrl && !alt && shift)) { - // Rotate - view.rotate(t, flipX * drot * dx, -flipY * drot * dy, 0); - } - - if((pan && left && !ctrl && !alt && !shift) || right || (left && ctrl && !alt && !shift)) { - // Pan - view.pan(t, -camera.translateSpeed * dx * distance, camera.translateSpeed * dy * distance, 0); - } - - if((zoom && left && !ctrl && !alt && !shift) || middle || (left && !ctrl && alt && !shift)) { - // Zoom - var kzoom = -camera.zoomSpeed * dy / window.innerHeight * (t - view.lastT()) * 100; - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); - } - - lastX = x; - lastY = y; - lastMods = mods; - - return true; - } - - camera.wheelListener = mouseWheel(element, function(dx, dy) { - // TODO remove now that we can disable scroll via scrollZoom? - if(camera.keyBindingMode === false) return; - if(!camera.enableWheel) return; - - var flipX = camera.flipX ? 1 : -1; - var flipY = camera.flipY ? 1 : -1; - var t = now(); - if(Math.abs(dx) > Math.abs(dy)) { - view.rotate(t, 0, 0, -dx * flipX * Math.PI * camera.rotateSpeed / window.innerWidth); - } else { - var kzoom = -camera.zoomSpeed * flipY * dy / window.innerHeight * (t - view.lastT()) / 20.0; - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); - } - }, true); - - return camera; -} diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index 14f4c596ada..c99979d7b98 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -48,6 +48,7 @@ exports.plot = function plotGl3d(gd) { var sceneId = sceneIds[i]; var fullSceneData = getSubplotData(fullData, GL3D, sceneId); var sceneLayout = fullLayout[sceneId]; + var camera = sceneLayout.camera; var scene = sceneLayout._scene; if(!scene) { @@ -56,7 +57,8 @@ exports.plot = function plotGl3d(gd) { graphDiv: gd, container: gd.querySelector('.gl-container'), staticPlot: gd._context.staticPlot, - plotGlPixelRatio: gd._context.plotGlPixelRatio + plotGlPixelRatio: gd._context.plotGlPixelRatio, + camera: camera }, fullLayout ); diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js index f9bb9236d3f..6b7934675eb 100644 --- a/src/plots/gl3d/layout/defaults.js +++ b/src/plots/gl3d/layout/defaults.js @@ -68,6 +68,7 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { coerce('camera.' + cameraKeys[j] + '.y'); coerce('camera.' + cameraKeys[j] + '.z'); } + coerce('camera.ortho'); /* * coerce to positive number (min 0) but also do not accept 0 (>0 not >=0) diff --git a/src/plots/gl3d/layout/layout_attributes.js b/src/plots/gl3d/layout/layout_attributes.js index 65699616547..99f6662f189 100644 --- a/src/plots/gl3d/layout/layout_attributes.js +++ b/src/plots/gl3d/layout/layout_attributes.js @@ -72,6 +72,16 @@ module.exports = { 'of this scene.' ].join(' ') }), + ortho: extendFlat({ + valType: 'boolean', + role: 'info', + dflt: false, + editType: 'camera', + description: [ + 'Enable/disable orthographic camera.', + 'Default is perspective.' + ].join(' ') + }), editType: 'camera' }, domain: domainAttrs({name: 'scene', editType: 'plot'}), diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 5d3ebe23297..da367576df1 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -9,6 +9,7 @@ 'use strict'; +var createCamera = require('3d-view-controls'); var createPlot = require('gl-plot3d'); var getContext = require('webgl-context'); var passiveSupported = require('has-passive-events'); @@ -22,7 +23,6 @@ var Fx = require('../../components/fx'); var str2RGBAarray = require('../../lib/str2rgbarray'); var showNoWebGlMsg = require('../../lib/show_no_webgl_msg'); -var createCamera = require('./camera'); var project = require('./project'); var createAxesOptions = require('./layout/convert'); var createSpikeOptions = require('./layout/spikes'); @@ -176,7 +176,7 @@ function render(scene) { scene.drawAnnotations(scene); } -function initializeGLPlot(scene, canvas, gl) { +function initializeGLPlot(scene, camera, canvas, gl) { var gd = scene.graphDiv; var glplotOptions = { @@ -188,7 +188,8 @@ function initializeGLPlot(scene, canvas, gl) { pickRadius: 10, snapToData: true, autoScale: true, - autoBounds: false + autoBounds: false, + camera: camera }; // for static plots, we reuse the WebGL context @@ -254,17 +255,7 @@ function initializeGLPlot(scene, canvas, gl) { }, false); } - if(!scene.camera) { - var cameraData = scene.fullSceneLayout.camera; - scene.camera = createCamera(scene.container, { - center: [cameraData.center.x, cameraData.center.y, cameraData.center.z], - eye: [cameraData.eye.x, cameraData.eye.y, cameraData.eye.z], - up: [cameraData.up.x, cameraData.up.y, cameraData.up.z], - zoomMin: 0.1, - zoomMax: 100, - mode: 'orbit' - }); - } + if(!scene.camera) scene.initializeGLCamera(); scene.glplot.camera = scene.camera; @@ -334,15 +325,32 @@ function Scene(options, fullLayout) { this.convertAnnotations = Registry.getComponentMethod('annotations3d', 'convert'); this.drawAnnotations = Registry.getComponentMethod('annotations3d', 'draw'); - if(!initializeGLPlot(this)) return; // todo check the necessity for this line + var camera = fullLayout.scene.camera; + initializeGLPlot(this, camera); } var proto = Scene.prototype; +proto.initializeGLCamera = function() { + + var cameraData = this.fullSceneLayout.camera; + + this.camera = createCamera(this.container, { + center: [cameraData.center.x, cameraData.center.y, cameraData.center.z], + eye: [cameraData.eye.x, cameraData.eye.y, cameraData.eye.z], + up: [cameraData.up.x, cameraData.up.y, cameraData.up.z], + ortho: cameraData.ortho, + zoomMin: 0.1, + zoomMax: 100, + mode: 'orbit' + }); +}; + proto.recoverContext = function() { var scene = this; var gl = this.glplot.gl; var canvas = this.glplot.canvas; + var camera = this.glplot.camera; this.glplot.dispose(); function tryRecover() { @@ -350,7 +358,7 @@ proto.recoverContext = function() { requestAnimationFrame(tryRecover); return; } - if(!initializeGLPlot(scene, canvas, gl)) { + if(!initializeGLPlot(scene, camera, canvas, gl)) { Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.'); return; } @@ -725,7 +733,8 @@ function getLayoutCamera(camera) { return { up: {x: camera.up[0], y: camera.up[1], z: camera.up[2]}, center: {x: camera.center[0], y: camera.center[1], z: camera.center[2]}, - eye: {x: camera.eye[0], y: camera.eye[1], z: camera.eye[2]} + eye: {x: camera.eye[0], y: camera.eye[1], z: camera.eye[2]}, + ortho: camera.ortho }; } @@ -765,6 +774,7 @@ proto.saveCamera = function saveCamera(layout) { } } } + if(cameraData.ortho !== cameraDataLastSave.ortho) hasChanged = true; } if(hasChanged) { diff --git a/test/jasmine/tests/gl_plot_interact_basic_test.js b/test/jasmine/tests/gl_plot_interact_basic_test.js index 9826f871085..951f68a4dae 100644 --- a/test/jasmine/tests/gl_plot_interact_basic_test.js +++ b/test/jasmine/tests/gl_plot_interact_basic_test.js @@ -13,7 +13,8 @@ var failTest = require('../assets/fail_test'); var cameraStructure = { up: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)}, center: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)}, - eye: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)} + eye: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)}, + ortho: jasmine.any(Boolean) }; function makePlot(gd, mock) {