QuintusHegie Posted September 21, 2018 Share Posted September 21, 2018 Hi guys and gals, I was reading the docs on Customizing Camera Inputs https://doc.babylonjs.com/how_to/customizing_camera_inputs and I want to customize the camera input for the FollowCamera. But it has no camera.inputs set at the moment when I look at the definition: https://github.com/BabylonJS/Babylon.js/blob/master/src/Cameras/babylon.followCamera.ts While for example the FreeCamera has: https://github.com/BabylonJS/Babylon.js/blob/master/src/Cameras/babylon.freeCamera.ts#L164 My question is: Can you set the camera.inputs to a new CameraInputsManager instance? Perhaps a DummyCameraInputsManager that's just an empty shell, doesn't need to do anything with input for now... just being there. So then I can add my own inputs to this camera using: camera.inputs.add(new MyFancyNewFollowCameraKeyboardInput()); I can then write camera input controllers (in JavaScript) that would adjust the FollowCamera's radius, rotationOffset and heightOffset. If you like my camera input controllers for the FollowCamera I can share the code, so they might even become default. ? Some background info: I use a FollowCamera in my BabylonJS Model Train Simulator game. It follows the train quite nicely, even in curves. ? But when the train gets longer, I want the user to be able to adjust the radius to get the train in view again (zoom in/out). Also when the player operates the train at a station, I want the user to be able to adjust the rotationOffset to get a clear view on loading/unloading the train at that station. So it's kinda like an ArcRotate input but slightly different. Let me know how I can help achieve this or if there's a similar solution that achieves more or less the same. Thanks, Quintus Quote Link to comment Share on other sites More sharing options...
Guest Posted September 21, 2018 Share Posted September 21, 2018 That's a good idea. No problem to create an input manager for the FollowCamera. Do you want to do a PR? Quote Link to comment Share on other sites More sharing options...
QuintusHegie Posted September 22, 2018 Author Share Posted September 22, 2018 17 hours ago, Deltakosh said: That's a good idea. No problem to create an input manager for the FollowCamera. Do you want to do a PR? Sure, if a Pull Request is the way to go to add code looks fine to me. Need some help though, because this will be my first Pull Request ever. Q Quote Link to comment Share on other sites More sharing options...
QuintusHegie Posted September 22, 2018 Author Share Posted September 22, 2018 Once the CameraInputsManager is there on the FollowCamera, this is my idea for the controls: What to control with input device camera.radius = desired distance to followed target camera.rotationOffset = desired rotation offset from axis of followed target (xz-plane / y-axis) in degrees camera.heightOffset = desired height offset from axis of followed target (xz-plane / y-axis) The FollowCamera's Up always remains Up with the world (avoids roller coaster looping sickness ? ). It's a Camera that moves through the world but tries to follow a target. A camera that fully aligns with the orientation vector of the followed object (e.g. a Plane viewed from it's tail) is a different type of camera. How to control that The controls should intuitively probably be such that they are relative to the view from the camera on the target. The sensitivity and/or deadzone of the controls should be configurable and perhaps also some option to invert axis for heightOffset. Here's my idea so far: Keyboard up (-) / down (+) to control radius left (?) / right (?) to control rotationOffset SHIFT + [up (+) / down (-)] to control heightOffset Mouse wheel forward (-) / wheel backward (+) scroll to control radius drag left (?) / right (?) to control rotationOffset drag up (?) / down (?) to control heightOffset Touch same as mouse but wheel is changed to 2 finger zoom in (2 fingers drag away from center to opposite side) / zoom out (2 fingers drag to center from opposite sides) Gamepad right stick Y up (-) / down (+) to control radius right stick X to control rotationOffset left stick Y to control heightOffset VirtualJoystick same as gamepad DeviceOrientation lean forward (-) / backward (+) to control radius rotate left/right around Up-axis to control rotationOffset Pointer no clue; perhaps point to a world location and then the new desired radius and rotationOffset is computed measured from current location locked target and pointed location, given the same desired heightOffset from the locked target's zx-plane? That's about what I figured thus far would fit in the experience of the game I'm making. Your comments are welcome so I can make the controls more generic and in conformance with the other already existing input controls. Q Quote Link to comment Share on other sites More sharing options...
Guest Posted September 24, 2018 Share Posted September 24, 2018 I have no use of this camera so I'm definitely not the right guy to provide guidance AS far as I can tell, we should start small by adding a first keyboard input controller to get a sense of what we want to achieve? The overall plan seems solid though Quote Link to comment Share on other sites More sharing options...
QuintusHegie Posted September 25, 2018 Author Share Posted September 25, 2018 (edited) On 9/24/2018 at 6:14 PM, Deltakosh said: I have no use of this camera so I'm definitely not the right guy to provide guidance AS far as I can tell, we should start small by adding a first keyboard input controller to get a sense of what we want to achieve? The overall plan seems solid though I'll bet you'll start to love this camera when you play my game when the new camera controls are ready ? Anyway, ok, something like this? FollowCameraKeyboardMoveInput: I wrote an untested example code for the Keyboard Input: /** * Keyboard input to control the 'radius' (up/down), 'rotationOffset' (left/right) and 'heightOffset' parameters of FollowCamera. * @see http://www.html5gamedevs.com/topic/40164-missing-camerainputmanager-support-for-followcamera-camerainputs/ */ var FollowCameraKeyboardMoveInput = function () { /** * Defines the camera the input is attached to. */ this.camera = null; /** * Gets or Set the list of keyboard keys used to control the forward move of the camera. */ this.keysUp = [38]; // Arrow Up key /** * Gets or Set the list of keyboard keys used to control the backward move of the camera. */ this.keysDown = [40]; // Arrow Down key /** * Gets or Set the list of keyboard keys used to control the left strafe move of the camera. */ this.keysLeft = [37]; // Arrow Left key /** * Gets or Set the list of keyboard keys used to control the right strafe move of the camera. */ this.keysRight = [39]; // Arrow Right key this._keys = []; this._shiftKey = false; // TODO let developer choose whether to use shiftKey, ctrlKey, altKey or metaKey this._onCanvasBlurObserver = null; this._onKeyboardObserver = null; this._engine = null; this._scene = null; }; /** * Attach the input controls to a specific dom element to get the input from. * @param element Defines the element the controls should be listened from * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) */ FollowCameraKeyboardMoveInput.prototype.attachControl = function(element, noPreventDefault) { if (this._onCanvasBlurObserver) return; this._scene = this.camera.getScene(); this._engine = this._scene.getEngine(); this._onCanvasBlurObserver = this._engine.onCanvasBlurObservable.add(() => { this._keys = []; this._shiftKey = false; }); this._onKeyboardObserver = this._scene.onKeyboardObservable.add(info => { let evt = info.event; // Store the shift key state this._shiftKey = (evt.shiftKey != false); if (info.type === KeyboardEventTypes.KEYDOWN) { // A key is pressed if (this.keysUp.indexOf(evt.keyCode) !== -1 || this.keysDown.indexOf(evt.keyCode) !== -1 || this.keysLeft.indexOf(evt.keyCode) !== -1 || this.keysRight.indexOf(evt.keyCode) !== -1) { var i = this._keys.indexOf(evt.keyCode); // Add the key to the list of pressed keys if (i === -1) this._keys.push(evt.keyCode); if (!noPreventDefault) evt.preventDefault(); } } else { // A key is no longer pressed if (this.keysUp.indexOf(evt.keyCode) !== -1 || this.keysDown.indexOf(evt.keyCode) !== -1 || this.keysLeft.indexOf(evt.keyCode) !== -1 || this.keysRight.indexOf(evt.keyCode) !== -1) { var i = this._keys.indexOf(evt.keyCode); // Remove the key to the list of pressed keys if (i >= 0) this._keys.splice(i, 1); if (!noPreventDefault) evt.preventDefault(); } } }); }; /** * Detach the current controls from the specified dom element. * @param element Defines the element to stop listening the inputs from */ FollowCameraKeyboardMoveInput.prototype.detachControl = function(element) { if (this._scene) { if (this._onKeyboardObserver) this._scene.onKeyboardObservable.remove(this._onKeyboardObserver); if (this._onCanvasBlurObserver) this._engine.onCanvasBlurObservable.remove(this._onCanvasBlurObserver); this._onKeyboardObserver = null; this._onCanvasBlurObserver = null; } this._keys = []; this._shiftKey = false; }; /** * Update the current camera state depending on the inputs that have been used this frame. * This is a dynamically created lambda to avoid the performance penalty of looping for inputs in the render loop. */ FollowCameraKeyboardMoveInput.prototype.checkInputs = function() { if (this._onKeyboardObserver) { // Keyboard for (var i = 0; i < this._keys.length; i++) { var keyCode = this._keys[i]; var speed = this.camera._computeLocalCameraSpeed(); if (this.keysLeft.indexOf(keyCode) !== -1) { // Left = rotate clockwise around target this.camera.rotationOffset = (this.camera.rotationOffset + -speed) % 360; } else if (this.keysUp.indexOf(keyCode) !== -1) { // Up = move closer to target (or with shift pressed: heighten altitude) if (this._shiftKey) this.camera.heightOffset += speed; else this.camera.radius += -speed; } else if (this.keysRight.indexOf(keyCode) !== -1) { // Right = rotate counter-clockwise around target this.camera.rotationOffset = (this.camera.rotationOffset + speed) % 360; } else if (this.keysDown.indexOf(keyCode) !== -1) { // Down = move further away from target (or with shift pressed: lower altitude) if (this._shiftKey) this.camera.heightOffset += -speed; else this.camera.radius += speed; } /* // TODO take this into account or not? if (this.camera.getScene().useRightHandedSystem) this.camera._localDirection.z *= -1; */ /* // TODO replace with updating the camera position and rotation... this.camera.getViewMatrix().invertToRef(this.camera._cameraTransformMatrix); Vector3.TransformNormalToRef(this.camera._localDirection, this.camera._cameraTransformMatrix, this.camera._transformedDirection); this.camera.cameraDirection.addInPlace(this.camera._transformedDirection); */ } } } /** * Gets the class name of the current intput. * @returns the class name */ FollowCameraKeyboardMoveInput.prototype.getClassName = function() { return "FollowCameraKeyboardMoveInput"; }; /** @hidden */ FollowCameraKeyboardMoveInput.prototype._onLostFocus = function (e) { this._keys = []; this._shiftKey = false; }; /** * Get the friendly name associated with the input class. * @returns the input friendly name */ FollowCameraKeyboardMoveInput.prototype.getSimpleName = function() { return "keyboard"; }; FollowCameraInputsManager: And the untested inputs manager to associate with it: /** * Default Inputs manager for the FollowCamera. * It groups all the default supported inputs for ease of use. * @version 1 Mouse only * @see http://www.html5gamedevs.com/topic/40164-missing-camerainputmanager-support-for-followcamera-camerainputs/ */ var FollowCameraInputsManager = function(camera) { BABYLON.CameraInputsManager.call(this, camera); }; FollowCameraInputsManager.prototype = Object.create(BABYLON.CameraInputsManager.prototype); FollowCameraInputsManager.prototype.constructor = FollowCameraInputsManager; /** * Add keyboard input support to the input manager. * @returns the current input manager */ FollowCameraInputsManager.prototype.addKeyboard = function() { this.add(new FollowCameraKeyboardMoveInput()); return this; }; /** * Add mouse input support to the input manager. * @returns the current input manager */ FollowCameraInputsManager.prototype.addMouse = function(touchEnabled = true) { //this.add(new FollowCameraMouseMoveInput(touchEnabled)); return this; } /** * Add orientation input support to the input manager. * @returns the current input manager */ FollowCameraInputsManager.prototype.addDeviceOrientation = function() { //this.add(new FollowCameraDeviceOrientationInput()); return this; } /** * Add touch input support to the input manager. * @returns the current input manager */ FollowCameraInputsManager.prototype.addTouch = function() { //this.add(new FollowCameraTouchInput()); return this; } /** * Add virtual joystick input support to the input manager. * @returns the current input manager */ FollowCameraInputsManager.prototype.addVirtualJoystick = function() { //this.add(new FollowCameraVirtualJoystickInput()); return this; } FollowCamera And then also some minor changes to the existing FollowCamera are needed to add support in the first place of this camera for working with an input manager... do I need to spell this syntax out? ? Bonus: FollowCameraMouseMoveInput Untested code for click+dragging and mouse wheel. Simply uncomment in above input manager to add support. /** * Mouse input to control the 'radius' (wheel), 'rotationOffset' (left/right drag) and 'heightOffset' (up/down drag) parameters of FollowCamera. * @see http://www.html5gamedevs.com/topic/40164-missing-camerainputmanager-support-for-followcamera-camerainputs/ */ var FollowCameraMouseMoveInput = function (touchEnabled = true) { /** * Defines the camera the input is attached to. */ this.camera = null; /** * Defines the buttons associated with the input to handle camera move. */ this.buttons = [0, 1, 2]; /** * Defines the pointer angular sensibility along the X and Y axis or how fast is the camera rotating. */ this.angularSensibility = 2000.0; /** * Gets or Set the mouse wheel precision or how fast is the camera zooming. */ this.wheelPrecision = 3.0; /** * wheelDeltaPercentage will be used instead of wheelPrecision if different from 0. * It defines the percentage of current camera.radius to use as delta when wheel is used. */ this.wheelDeltaPercentage = 0; /** * Define if touch is enabled in the mouse input */ this.touchEnabled = touchEnabled; this._pointerInput = null; this._onMouseMove = null; this._observer = null; this.previousPosition = null; }; /** * Attach the input controls to a specific dom element to get the input from. * @param element Defines the element the controls should be listened from * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) */ FollowCameraMouseMoveInput.prototype.attachControl = function (element, noPreventDefault) { var engine = this.camera.getEngine(); if (!this._pointerInput) { this._pointerInput = (p, s) => { var evt = p.event; if (engine.isInVRExclusivePointerMode) return; if (!this.touchEnabled && evt.pointerType === "touch") return; // FIXME update this condition for wheel if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(evt.button) === -1) return; let srcElement = (evt.srcElement || evt.target); if (p.type === PointerEventTypes.POINTERDOWN && srcElement) { try { srcElement.setPointerCapture(evt.pointerId); } catch (e) { //Nothing to do with the error. Execution will continue. } this.previousPosition = { x: evt.clientX, y: evt.clientY }; if (!noPreventDefault) { evt.preventDefault(); element.focus(); } } else if (p.type === PointerEventTypes.POINTERUP && srcElement) { try { srcElement.releasePointerCapture(evt.pointerId); } catch (e) { //Nothing to do with the error. } this.previousPosition = null; if (!noPreventDefault) evt.preventDefault(); } else if (p.type === PointerEventTypes.POINTERMOVE) { if (!this.previousPosition || engine.isPointerLock) return; var offsetX = evt.clientX - this.previousPosition.x; if (this.camera.getScene().useRightHandedSystem) offsetX *= -1; if (this.camera.parent && this.camera.parent._getWorldMatrixDeterminant() < 0) offsetX *= -1; this.camera.rotationOffset += offsetX / this.angularSensibility; var offsetY = evt.clientY - this.previousPosition.y; this.camera.heightOffset += offsetY / this.angularSensibility; this.previousPosition = { x: evt.clientX, y: evt.clientY }; if (!noPreventDefault) evt.preventDefault(); } else if (p.type === PointerEventTypes.POINTERWHEEL) { var delta = 0; if (evt.wheelDelta) { if (this.wheelDeltaPercentage) { var wheelDelta = (evt.wheelDelta * 0.01 * this.wheelDeltaPercentage) * this.camera.radius; if (evt.wheelDelta > 0) { delta = wheelDelta / (1.0 + this.wheelDeltaPercentage); } else { delta = wheelDelta * (1.0 + this.wheelDeltaPercentage); } } else { delta = evt.wheelDelta / (this.wheelPrecision * 40); } } else if (evt.detail) { delta = -evt.detail / this.wheelPrecision; } if (delta) this.camera.radius += delta; if (evt.preventDefault) { if (!noPreventDefault) evt.preventDefault(); } } }; } if (!this._onMouseMove) { this._onMouseMove = evt => { if (!engine.isPointerLock) return; if (engine.isInVRExclusivePointerMode) return; var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0; if (this.camera.getScene().useRightHandedSystem) offsetX *= -1; if (this.camera.parent && this.camera.parent._getWorldMatrixDeterminant() < 0) offsetX *= -1; this.camera.rotationOffset += offsetX / this.angularSensibility; var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0; this.camera.heightOffset += offsetY / this.angularSensibility; this.previousPosition = null; if (!noPreventDefault) evt.preventDefault(); }; } this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput, PointerEventTypes.POINTERDOWN | PointerEventTypes.POINTERUP | PointerEventTypes.POINTERMOVE | PointerEventTypes.POINTERWHEEL); element.addEventListener("mousemove", this._onMouseMove, false); } /** * Detach the current controls from the specified dom element. * @param element Defines the element to stop listening the inputs from */ FollowCameraMouseMoveInput.prototype.detachControl = function (element) { if (this._observer && element) { this.camera.getScene().onPointerObservable.remove(this._observer); if (this._onMouseMove) element.removeEventListener("mousemove", this._onMouseMove); this._observer = null; this._onMouseMove = null; this.previousPosition = null; } } /** * Gets the class name of the current intput. * @returns the class name */ FollowCameraMouseMoveInput.prototype.getClassName = function () { return "FollowCameraMouseMoveInput"; } /** * Get the friendly name associated with the input class. * @returns the input friendly name */ FollowCameraMouseMoveInput.prototype.getSimpleName = function () { return "mouse"; } Q Edited September 25, 2018 by QuintusHegie added mouse input template as well Quote Link to comment Share on other sites More sharing options...
Guest Posted September 25, 2018 Share Posted September 25, 2018 This will need to be in TS in order to be merged (read this as a good kickstarter: http://doc.babylonjs.com/how_to/how_to_start) Quote Link to comment Share on other sites More sharing options...
QuintusHegie Posted September 29, 2018 Author Share Posted September 29, 2018 On 9/25/2018 at 10:00 PM, Deltakosh said: This will need to be in TS in order to be merged (read this as a good kickstarter: http://doc.babylonjs.com/how_to/how_to_start) Ok. Thanks for the kickstarter guide. This will take me some time for me to learn and get started with, but seems doable. Q GameMonetize 1 Quote Link to comment Share on other sites More sharing options...
dunk Posted January 6, 2019 Share Posted January 6, 2019 (edited) Here's a start that adds keyboard bindings to `FollowCamera`: https://github.com/mrdunk/Babylon.js/commit/e0cecc50caf425bee0b5d4ffbb68bc74ed480214 Is this something that's worth pushing? I've modeled it after `arcRotateCameraInputsManager`. I'm new to Babylon so feel free to make suggestions. To test, put the following in `localDev/src/index.js` or Playground: var createScene = function () { // This creates a basic Babylon Scene object (non-mesh) var scene = new BABYLON.Scene(engine); // Our built-in 'sphere' shape. Params: name, subdivs, size, scene var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene); var frameRate = 10; var xSlide = new BABYLON.Animation("xSlide", "position.x", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE); var keyFrames = [ {frame: 0, value: 2}, {frame: frameRate, value: -2}, {frame: 2 * frameRate, value: 2} ]; xSlide.setKeys(keyFrames); scene.beginDirectAnimation(sphere, [xSlide], 0, 2 * frameRate, true); // This creates and positions a free camera (non-mesh) var camera = new BABYLON.FollowCamera("camera1", new BABYLON.Vector3(0, 0, 0), scene); camera.lockedTarget = sphere; // This targets the camera to scene origin camera.setTarget(BABYLON.Vector3.Zero()); // This attaches the camera to the canvas camera.attachControl(canvas, true); // This creates a light, aiming 0,1,0 - to the sky (non-mesh) var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); // Default intensity is 1. Let's dim the light a small amount light.intensity = 0.7; // Move the sphere upward 1/2 its height sphere.position.y = 1; // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene); return scene; }; dunk. Edited January 6, 2019 by dunk added test example. Quote Link to comment Share on other sites More sharing options...
dunk Posted January 6, 2019 Share Posted January 6, 2019 I just discovered the new forum. Let's use that instead. https://forum.babylonjs.com/t/camerainputmanager-support-for-followcamera/592 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.