diff --git a/js/src/core/Core.js b/js/src/core/Core.js index a6275c5b..8cb35024 100644 --- a/js/src/core/Core.js +++ b/js/src/core/Core.js @@ -3,6 +3,7 @@ const msgpack = require('msgpack-lite'); const LilGUI = require('lil-gui').GUI; const {viewModes} = require('./lib/viewMode'); +const {cameraUpAxisModes} = require('./lib/cameraUpAxis'); const _ = require('../lodash'); const {cameraModes} = require('./lib/cameraMode'); const loader = require('./lib/Loader'); @@ -14,6 +15,7 @@ const detachWindowGUI = require('./lib/detachWindow'); const fullscreen = require('./lib/fullscreen'); const {viewModeGUI} = require('./lib/viewMode'); const {cameraModeGUI} = require('./lib/cameraMode'); +const {cameraUpAxisGUI} = require('./lib/cameraUpAxis'); const manipulate = require('./lib/manipulate'); const {getColorLegend} = require('./lib/colorMapLegend'); const objectsGUIProvider = require('./lib/objectsGUIprovider'); @@ -132,6 +134,7 @@ function K3D(provider, targetDOMNode, parameters) { viewModeGUI(GUI.controls, self); cameraModeGUI(GUI.controls, self); + cameraUpAxisGUI(GUI.controls, self); manipulate.manipulateGUI(GUI.controls, self, changeParameters); GUI.controls.add(self.parameters, 'cameraFov').step(0.1).min(1.0).max(179) @@ -299,6 +302,7 @@ function K3D(provider, targetDOMNode, parameters) { cameraDampingFactor: 0.0, name: null, cameraFov: 60.0, + cameraUpAxis: cameraUpAxisModes.none, cameraAnimation: {}, autoRendering: true, axesHelper: 1.0, @@ -722,14 +726,29 @@ function K3D(provider, targetDOMNode, parameters) { self.parameters.cameraDampingFactor = factor; self.getWorld().changeControls(true); + }; + + /** + * Set camera up axis + * @memberof K3D.Core + * @param {String} axis + */ + this.setCameraUpAxis = function (axis) { + self.parameters.cameraUpAxis = axis; + + self.getWorld().changeControls(true); if (GUI.controls) { GUI.controls.controllers.forEach((controller) => { - if (controller.property === 'damping_factor') { + if (controller.property === 'cameraUpAxis') { controller.updateDisplay(); } }); } + + self.rebuildSceneData(false).then(() => { + self.render(); + }); }; /** @@ -1268,6 +1287,7 @@ function K3D(provider, targetDOMNode, parameters) { self.setGrid(self.parameters.grid); self.setCameraAutoFit(self.parameters.cameraAutoFit); self.setCameraDampingFactor(self.parameters.cameraDampingFactor); + self.setCameraUpAxis(self.parameters.cameraUpAxis); self.setClippingPlanes(self.parameters.clippingPlanes); self.setDirectionalLightingIntensity(self.parameters.lighting); self.setColorMapLegend(self.parameters.colorbarObjectId); diff --git a/js/src/core/lib/cameraUpAxis.js b/js/src/core/lib/cameraUpAxis.js new file mode 100644 index 00000000..c2407308 --- /dev/null +++ b/js/src/core/lib/cameraUpAxis.js @@ -0,0 +1,40 @@ +const cameraUpAxisModes = { + x: 'x', + y: 'y', + z: 'z', + none: 'none' +}; + +function cameraUpAxisGUI(gui, K3D) { + gui.add(K3D.parameters, 'cameraUpAxis', { + x: cameraUpAxisModes.x, + y: cameraUpAxisModes.y, + z: cameraUpAxisModes.z, + none: cameraUpAxisModes.none + }).name('CameraUpAxis').onChange( + (axis) => { + K3D.setCameraUpAxis(axis); + + K3D.dispatch(K3D.events.PARAMETERS_CHANGE, { + key: 'camera_up_axis', + value: axis + }); + }, + ); +} + +function setupUpVector(camera, cameraUpAxis) { + if (cameraUpAxis === cameraUpAxisModes.x) { + camera.up.set(1, 0, 0); + } else if (cameraUpAxis === cameraUpAxisModes.y) { + camera.up.set(0, 1, 0); + } else if (cameraUpAxis === cameraUpAxisModes.z) { + camera.up.set(0, 0, 1); + } +} + +module.exports = { + cameraUpAxisGUI, + cameraUpAxisModes, + setupUpVector +}; diff --git a/js/src/k3d.js b/js/src/k3d.js index 7f0efa4c..e244b70c 100644 --- a/js/src/k3d.js +++ b/js/src/k3d.js @@ -310,6 +310,7 @@ class PlotView extends widgets.DOMWidgetView { this.model.on('change:camera_pan_speed', this._setCameraSpeeds, this); this.model.on('change:camera_fov', this._setCameraFOV, this); this.model.on('change:camera_damping_factor', this._setCameraDampingFactor, this); + this.model.on('change:camera_up_axis', this._setCameraUpAxis, this); this.model.on('change:axes_helper', this._setAxesHelper, this); this.model.on('change:axes_helper_colors', this._setAxesHelperColors, this); this.model.on('change:snapshot_type', this._setSnapshotType, this); @@ -338,6 +339,7 @@ class PlotView extends widgets.DOMWidgetView { cameraPanSpeed: this.model.get('camera_pan_speed'), cameraDampingFactor: this.model.get('camera_damping_factor'), cameraFov: this.model.get('camera_fov'), + cameraUpAxis: this.model.get('camera_up_axis'), colorbarObjectId: this.model.get('colorbar_object_id'), cameraAnimation: this.model.get('camera_animation'), name: this.model.get('name'), @@ -563,6 +565,10 @@ class PlotView extends widgets.DOMWidgetView { this.K3DInstance.setCameraDampingFactor(this.model.get('camera_damping_factor')); }; + _setCameraUpAxis() { + this.K3DInstance.setCameraUpAxis(this.model.get('camera_up_axis')); + }; + _setClippingPlanes() { this.K3DInstance.setClippingPlanes(this.model.get('clipping_planes')); }; diff --git a/js/src/providers/threejs/helpers/THREE.OrbitControls.js b/js/src/providers/threejs/helpers/THREE.OrbitControls.js index 09f23462..0f49ca07 100644 --- a/js/src/providers/threejs/helpers/THREE.OrbitControls.js +++ b/js/src/providers/threejs/helpers/THREE.OrbitControls.js @@ -1,6 +1,8 @@ +const { setupUpVector } = require('./../../../core/lib/cameraUpAxis'); + /* eslint-disable */ module.exports = function (THREE) { - THREE.OrbitControls = function (object, domElement) { + THREE.OrbitControls = function (object, domElement, K3D) { if (domElement !== undefined) { this.domElement = domElement; @@ -144,10 +146,11 @@ module.exports = function (THREE) { // this method is exposed, but perhaps it would be better if we can make it private... this.update = function (silent) { - const offset = new THREE.Vector3(); // so camera.up is the orbit axis + setupUpVector(object, K3D.parameters.cameraUpAxis); + const quat = new THREE.Quaternion().setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0)); const quatInverse = quat.clone().invert(); diff --git a/js/src/providers/threejs/helpers/THREE.TrackballControls.js b/js/src/providers/threejs/helpers/THREE.TrackballControls.js index 9c29eb8e..3746a996 100644 --- a/js/src/providers/threejs/helpers/THREE.TrackballControls.js +++ b/js/src/providers/threejs/helpers/THREE.TrackballControls.js @@ -1,7 +1,8 @@ /* eslint-disable */ +const { setupUpVector } = require('./../../../core/lib/cameraUpAxis'); module.exports = function (THREE) { - THREE.TrackballControls = function (object, domElement) { + THREE.TrackballControls = function (object, domElement, K3D) { const scope = this; const STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4, @@ -46,7 +47,7 @@ module.exports = function (THREE) { this.minDistance = 0; this.maxDistance = Infinity; - this.mouseButtons = {LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN}; + this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; // internals @@ -91,9 +92,9 @@ module.exports = function (THREE) { // events - const _changeEvent = {type: 'change'}; - const _startEvent = {type: 'start'}; - const _endEvent = {type: 'end'}; + const _changeEvent = { type: 'change' }; + const _startEvent = { type: 'start' }; + const _endEvent = { type: 'end' }; // methods @@ -292,6 +293,8 @@ module.exports = function (THREE) { }; this.update = function (silent) { + setupUpVector(scope.object, K3D.parameters.cameraUpAxis); + _eye.subVectors(scope.object.position, scope.target); if (!scope.noRotate) { @@ -648,7 +651,7 @@ module.exports = function (THREE) { this.domElement.addEventListener('pointerdown', onPointerDown); this.domElement.addEventListener('pointercancel', onPointerCancel); - this.domElement.addEventListener('wheel', onMouseWheel, {passive: false}); + this.domElement.addEventListener('wheel', onMouseWheel, { passive: false }); this.domElement.addEventListener('contextmenu', contextmenu); currentDocument.addEventListener('keydown', keydown); diff --git a/js/src/providers/threejs/initializers/Camera.js b/js/src/providers/threejs/initializers/Camera.js index 5c8b352f..94ab497f 100644 --- a/js/src/providers/threejs/initializers/Camera.js +++ b/js/src/providers/threejs/initializers/Camera.js @@ -40,6 +40,7 @@ module.exports = function (K3D) { if (array.length === 9) { this.controls.object.up.fromArray(array, 6); + this.axesHelper.camera.up.copy(this.controls.object.up); } this.controls.target.fromArray(array, 3); diff --git a/js/src/providers/threejs/initializers/Canvas.js b/js/src/providers/threejs/initializers/Canvas.js index 4b1a86ea..55911d70 100644 --- a/js/src/providers/threejs/initializers/Canvas.js +++ b/js/src/providers/threejs/initializers/Canvas.js @@ -34,7 +34,7 @@ function addEvents(self, K3D, controls) { } function createTrackballControls(self, K3D) { - const controls = new THREE.TrackballControls(self.camera, self.renderer.domElement); + const controls = new THREE.TrackballControls(self.camera, self.renderer.domElement, K3D); controls.type = cameraModes.trackball; controls.rotateSpeed = K3D.parameters.cameraRotateSpeed; @@ -54,7 +54,7 @@ function createTrackballControls(self, K3D) { } function createOrbitControls(self, K3D) { - const controls = new THREE.OrbitControls(self.camera, self.renderer.domElement); + const controls = new THREE.OrbitControls(self.camera, self.renderer.domElement, K3D); controls.type = cameraModes.orbit; controls.rotateSpeed = K3D.parameters.cameraRotateSpeed; diff --git a/k3d/factory.py b/k3d/factory.py index 8a747b27..12d6c11b 100644 --- a/k3d/factory.py +++ b/k3d/factory.py @@ -2058,6 +2058,7 @@ def plot( camera_zoom_speed=1.2, camera_pan_speed=0.3, camera_damping_factor=0.0, + camera_up_axis='none', fps=25.0, minimum_fps=-1, fps_meter=False, @@ -2130,6 +2131,18 @@ def plot( Camera pan speed, by default 0.3. camera_damping_factor : float, optional Camera intensity of damping, by default 0.0. + camera_up_axis: `str`. + Fixed up axis for camera. + + Legal values are: + + :`x`: x axis, + + :`y`: y axis, + + :`z`: z axis, + + :`none`: Handling click_callback and hover_callback on some type of objects. fps : float, optional Animations FPS, by default 25.0. minimum_fps: `Float`. @@ -2173,6 +2186,7 @@ def plot( camera_zoom_speed=camera_zoom_speed, camera_damping_factor=camera_damping_factor, camera_pan_speed=camera_pan_speed, + camera_up_axis=camera_up_axis, auto_rendering=auto_rendering, fps=fps, minimum_fps=minimum_fps, diff --git a/k3d/plot.py b/k3d/plot.py index d3c3cc42..2722a331 100644 --- a/k3d/plot.py +++ b/k3d/plot.py @@ -60,6 +60,18 @@ class Plot(widgets.DOMWidget): Camera Field of View. camera_damping_factor: `Float`. Defines the intensity of damping. Default is 0 (disabled). + camera_up_axis: `str`. + Fixed up axis for camera. + + Legal values are: + + :`x`: x axis, + + :`y`: y axis, + + :`z`: z axis, + + :`none`: Handling click_callback and hover_callback on some type of objects. snapshot_type: `string`. Can be 'full', 'online' or 'inline'. axes: `list`. @@ -159,6 +171,7 @@ class Plot(widgets.DOMWidget): camera_zoom_speed = Float().tag(sync=True) camera_pan_speed = Float().tag(sync=True) camera_damping_factor = Float().tag(sync=True) + camera_up_axis = Unicode().tag(sync=True) clipping_planes = ListOrArray(empty_ok=True).tag(sync=True) colorbar_object_id = Int(-1).tag(sync=True) colorbar_scientific = Bool(False).tag(sync=True) @@ -203,6 +216,7 @@ def __init__( camera_rotate_speed=1.0, camera_zoom_speed=1.2, camera_pan_speed=0.3, + camera_up_axis='none', snapshot_type='full', camera_no_pan=False, camera_fov=45.0, @@ -252,6 +266,7 @@ def __init__( self.camera_pan_speed = camera_pan_speed self.camera_damping_factor = camera_damping_factor self.camera_fov = camera_fov + self.camera_up_axis = camera_up_axis self.axes = axes self.axes_helper = axes_helper self.axes_helper_colors = axes_helper_colors @@ -498,6 +513,7 @@ def get_plot_params(self): "cameraZoomSpeed": self.camera_zoom_speed, "cameraPanSpeed": self.camera_pan_speed, "cameraDampingFactor": self.camera_damping_factor, + "cameraUpAxis": self.camera_up_axis, "name": self.name, "height": self.height, "cameraFov": self.camera_fov, diff --git a/package.json b/package.json index 6156a2d5..e4ca5b3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "k3d", - "version": "2.16.0", + "version": "2.17.0", "description": "3D visualization library", "keywords": [ "jupyter",