// // gracefulControls.js // examples // // Created by Ryan Huffman on 9/11/14 // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var DEFAULT_PARAMETERS = { // Coefficient to use for linear drag. Higher numbers will cause motion to // slow down more quickly. DRAG_COEFFICIENT: 60.0, MAX_SPEED: 10.0, ACCELERATION: 10.0, MOUSE_YAW_SCALE: -0.125, MOUSE_PITCH_SCALE: -0.125, MOUSE_SENSITIVITY: 0.5, // Damping frequency, adjust to change mouse look behavior W: 2.2, } var BRAKE_PARAMETERS = { DRAG_COEFFICIENT: 4.9, MAX_SPEED: DEFAULT_PARAMETERS.MAX_SPEED, ACCELERATION: 0, W: 1.0, MOUSE_YAW_SCALE: -0.125, MOUSE_PITCH_SCALE: -0.125, MOUSE_SENSITIVITY: 0.5, } var DRIVE_AVATAR_ENABLED = true; var UPDATE_RATE = 90; var USE_INTERVAL = true; var movementParameters = DEFAULT_PARAMETERS; // Movement keys var KEY_BRAKE = "Q"; var KEY_FORWARD = "W"; var KEY_BACKWARD = "S"; var KEY_LEFT = "A"; var KEY_RIGHT = "D"; var KEY_UP = "E"; var KEY_DOWN = "C"; var KEY_TOGGLE= "Space"; var KEYS; if (DRIVE_AVATAR_ENABLED) { KEYS = [KEY_BRAKE, KEY_FORWARD, KEY_BACKWARD, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN]; } else { KEYS = []; } // Global Variables var keys = {}; var velocity = { x: 0, y: 0, z: 0 }; var velocityVertical = 0; var enabled = false; var pos = Reticle.getPosition(); var lastX = pos.x; var lastY = pos.y; var yawFromMouse = 0; var pitchFromMouse = 0; var yawSpeed = 0; var pitchSpeed = 0; function update(dt) { if (enabled && Window.hasFocus()) { var pos = Reticle.getPosition(); var x = pos.x; var y = pos.y; var dx = x - lastX; var dy = y - lastY; yawFromMouse += (dx * movementParameters.MOUSE_YAW_SCALE * movementParameters.MOUSE_SENSITIVITY); pitchFromMouse += (dy * movementParameters.MOUSE_PITCH_SCALE * movementParameters.MOUSE_SENSITIVITY); pitchFromMouse = Math.max(-180, Math.min(180, pitchFromMouse)); resetCursorPosition(); } // Here we use a linear damping model - http://en.wikipedia.org/wiki/Damping#Linear_damping // Because we are using a critically damped model (no oscillation), ΞΆ = 1 and // so we derive the formula: acceleration = -(2 * w0 * v) - (w0^2 * x) var W = movementParameters.W; yawAccel = (W * W * yawFromMouse) - (2 * W * yawSpeed); pitchAccel = (W * W * pitchFromMouse) - (2 * W * pitchSpeed); yawSpeed += yawAccel * dt; var yawMove = yawSpeed * dt; var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees( { x: 0, y: yawMove, z: 0 } )); MyAvatar.orientation = newOrientation; yawFromMouse -= yawMove; pitchSpeed += pitchAccel * dt; var pitchMove = pitchSpeed * dt; var newPitch = MyAvatar.headPitch + pitchMove; MyAvatar.headPitch = newPitch; pitchFromMouse -= pitchMove; if (DRIVE_AVATAR_ENABLED) { var targetVelocity = { x: 0, y: 0, z: 0 }; var targetVelocityVertical = 0; var acceleration = movementParameters.ACCELERATION; if (keys[KEY_FORWARD]) { targetVelocity.z -= acceleration * dt; } if (keys[KEY_LEFT]) { targetVelocity.x -= acceleration * dt; } if (keys[KEY_BACKWARD]) { targetVelocity.z += acceleration * dt; } if (keys[KEY_RIGHT]) { targetVelocity.x += acceleration * dt; } if (keys[KEY_UP]) { targetVelocityVertical += acceleration * dt; } if (keys[KEY_DOWN]) { targetVelocityVertical -= acceleration * dt; } // If force isn't being applied in a direction, add drag; var drag = Math.max(movementParameters.DRAG_COEFFICIENT * dt, 1.0); if (targetVelocity.x == 0) { targetVelocity.x = -velocity.x * drag; } if (targetVelocity.z == 0) { targetVelocity.z = -velocity.z * drag; } velocity = Vec3.sum(velocity, targetVelocity); var maxSpeed = movementParameters.MAX_SPEED; velocity.x = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.x)); velocity.z = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.z)); var v = Vec3.multiplyQbyV(MyAvatar.headOrientation, velocity); if (targetVelocityVertical == 0) { targetVelocityVertical -= (velocityVertical * movementParameters.DRAG_COEFFICIENT * dt); } velocityVertical += targetVelocityVertical; velocityVertical = Math.max(-maxSpeed, Math.min(maxSpeed, velocityVertical)); v.y += velocityVertical; MyAvatar.motorVelocity = v; } } function vecToString(vec) { return vec.x + ", " + vec.y + ", " + vec.z; } function resetCursorPosition() { var newX = Math.floor(Window.innerWidth / 2); var newY = Math.floor(Window.innerHeight / 2); Reticle.setPosition({ x: newX, y: newY }); lastX = newX; lastY = newY; } function toggleEnabled() { if (enabled) { disable(); } else { enable(); } } var timerID = null; function enable() { if (!enabled && Window.hasFocus()) { enabled = true; resetCursorPosition(); // Reset movement variables yawFromMouse = 0; pitchFromMouse = 0; yawSpeed = 0; pitchSpeed = 0; velocityVertical = 0; velocity = { x: 0, y: 0, z: 0 }; MyAvatar.motorReferenceFrame = 'world'; MyAvatar.motorVelocity = { x: 0, y: 0, z: 0 }; MyAvatar.motorTimescale = 1; Controller.enableMapping(MAPPING_KEYS_NAME); Reticle.setVisible(false); if (USE_INTERVAL) { var lastTime = Date.now(); timerID = Script.setInterval(function() { var now = Date.now(); var dt = now - lastTime; lastTime = now; update(dt / 1000); }, (1.0 / UPDATE_RATE) * 1000); } else { Script.update.connect(update); } } } function disable() { if (enabled) { enabled = false; Reticle.setVisible(true); MyAvatar.motorVelocity = { x: 0, y: 0, z: 0 }; Controller.disableMapping(MAPPING_KEYS_NAME); if (USE_INTERVAL) { Script.clearInterval(timerID); timerID = null; } else { Script.update.disconnect(update); } } } function scriptEnding() { disable(); Controller.disableMapping(MAPPING_ENABLE_NAME); Controller.disableMapping(MAPPING_KEYS_NAME); } var MAPPING_ENABLE_NAME = 'io.highfidelity.gracefulControls.toggle'; var MAPPING_KEYS_NAME = 'io.highfidelity.gracefulControls.keys'; var keyControllerMapping = Controller.newMapping(MAPPING_KEYS_NAME); var enableControllerMapping = Controller.newMapping(MAPPING_ENABLE_NAME); function onKeyPress(key, value) { print(key, value); keys[key] = value > 0; if (value > 0) { if (key == KEY_TOGGLE) { toggleEnabled(); } else if (key == KEY_BRAKE) { movementParameters = BRAKE_PARAMETERS; } } else { if (key == KEY_BRAKE) { movementParameters = DEFAULT_PARAMETERS; } } } for (var i = 0; i < KEYS.length; ++i) { var key = KEYS[i]; var hw = Controller.Hardware.Keyboard[key]; if (hw) { keyControllerMapping.from(hw).to(function(key) { return function(value) { onKeyPress(key, value); }; }(key)); } else { print("Unknown key: ", key); } } enableControllerMapping.from(Controller.Hardware.Keyboard[KEY_TOGGLE]).to(function(value) { onKeyPress(KEY_TOGGLE, value); }); Controller.enableMapping(MAPPING_ENABLE_NAME); Script.scriptEnding.connect(scriptEnding);