Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels

This commit is contained in:
Andrzej Kapolka 2014-08-21 16:00:30 -07:00
commit 530f5b0df8
36 changed files with 1896 additions and 349 deletions

203
examples/Recorder.js Normal file
View file

@ -0,0 +1,203 @@
//
// Recorder.js
// examples
//
// Created by Clément Brisset on 8/20/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
//
Script.include("toolBars.js");
var recordingFile = "recording.rec";
var windowDimensions = Controller.getViewportDimensions();
var TOOL_ICON_URL = "http://s3-us-west-1.amazonaws.com/highfidelity-public/images/tools/";
var ALPHA_ON = 1.0;
var ALPHA_OFF = 0.7;
var COLOR_ON = { red: 128, green: 0, blue: 0 };
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
Tool.IMAGE_WIDTH *= 0.7;
Tool.IMAGE_HEIGHT *= 0.7;
var toolBar = null;
var recordIcon;
var playIcon;
var saveIcon;
var loadIcon;
setupToolBar();
var timer = null;
setupTimer();
function setupToolBar() {
if (toolBar != null) {
print("Multiple calls to Recorder.js:setupToolBar()");
return;
}
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL);
toolBar.setBack(COLOR_OFF, ALPHA_OFF);
recordIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "record.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false);
playIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "play.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false, false);
saveIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "save.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false, false);
loadIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "load.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false, false);
}
function setupTimer() {
timer = Overlays.addOverlay("text", {
font: { size: 20 },
text: (0.00).toFixed(3),
backgroundColor: COLOR_OFF,
x: 0, y: 0,
width: 100,
height: 100,
alpha: 1.0,
visible: true
});
}
function updateTimer() {
var text = "";
if (MyAvatar.isRecording()) {
text = formatTime(MyAvatar.recorderElapsed())
} else {
text = formatTime(MyAvatar.playerElapsed()) + " / " +
formatTime(MyAvatar.playerLength());
}
Overlays.editOverlay(timer, {
text: text
})
}
function formatTime(time) {
var MIN_PER_HOUR = 60;
var SEC_PER_MIN = 60;
var MSEC_PER_SEC = 1000;
var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR));
time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR);
var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN));
time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN);
var seconds = Math.floor(time / MSEC_PER_SEC);
seconds = time / MSEC_PER_SEC;
var text = "";
text += (hours > 0) ? hours + ":" :
"";
text += (minutes > 0) ? ((minutes < 10 && text != "") ? "0" : "") + minutes + ":" :
"";
text += ((seconds < 10 && text != "") ? "0" : "") + seconds.toFixed(3);
return text;
}
function moveUI() {
var relative = { x: 30, y: 90 };
toolBar.move(relative.x,
windowDimensions.y - relative.y);
Overlays.editOverlay(timer, {
x: relative.x - 10,
y: windowDimensions.y - relative.y - 35,
width: 0,
height: 0
});
}
function mousePressEvent(event) {
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (recordIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
MyAvatar.startRecording();
toolBar.setBack(COLOR_ON, ALPHA_ON);
} else {
MyAvatar.stopRecording();
MyAvatar.loadLastRecording();
toolBar.setBack(COLOR_OFF, ALPHA_OFF);
}
} else if (playIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying();
} else {
MyAvatar.startPlaying();
}
}
} else if (saveIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
recordingFile = Window.save("Save recording to file", ".", "*.rec");
MyAvatar.saveRecording(recordingFile);
}
} else if (loadIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
recordingFile = Window.browse("Load recorcding from file", ".", "*.rec");
MyAvatar.loadRecording(recordingFile);
}
} else {
}
}
function update() {
var newDimensions = Controller.getViewportDimensions();
if (windowDimensions.x != newDimensions.x ||
windowDimensions.y != newDimensions.y) {
windowDimensions = newDimensions;
moveUI();
}
updateTimer();
}
function scriptEnding() {
if (MyAvatar.isRecording()) {
MyAvatar.stopRecording();
}
if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying();
}
toolBar.cleanup();
Overlays.deleteOverlay(timer);
}
Controller.mousePressEvent.connect(mousePressEvent);
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);
// Should be called last to put everything into position
moveUI();

443
examples/frisbee.js Normal file
View file

@ -0,0 +1,443 @@
//
// frisbee.js
// examples
//
// Created by Thijs Wenker on 7/5/14.
// Copyright 2014 High Fidelity, Inc.
//
// Requirements: Razer Hydra's
//
// Fun game to throw frisbee's to eachother. Hold the trigger on any of the hydra's to create or catch a frisbee.
//
// Tip: use this together with the squeezeHands.js script to make it look nicer.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("toolBars.js");
const LEFT_PALM = 0;
const LEFT_TIP = 1;
const LEFT_BUTTON_FWD = 5;
const LEFT_BUTTON_3 = 3;
const RIGHT_PALM = 2;
const RIGHT_TIP = 3;
const RIGHT_BUTTON_FWD = 11;
const RIGHT_BUTTON_3 = 9;
const FRISBEE_RADIUS = 0.08;
const GRAVITY_STRENGTH = 0.5;
const CATCH_RADIUS = 0.5;
const MIN_SIMULATION_SPEED = 0.15;
const THROWN_VELOCITY_SCALING = 1.5;
const SOUNDS_ENABLED = true;
const FRISBEE_BUTTON_URL = "http://test.thoys.nl/hifi/images/frisbee/frisbee_button_by_Judas.svg";
const FRISBEE_MODEL_SCALE = 275;
const FRISBEE_MENU = "Toys>Frisbee";
const FRISBEE_DESIGN_MENU = "Toys>Frisbee>Design";
const FRISBEE_ENABLED_SETTING = "Frisbee>Enabled";
const FRISBEE_CREATENEW_SETTING = "Frisbee>CreateNew";
const FRISBEE_DESIGN_SETTING = "Frisbee>Design";
const FRISBEE_FORCE_MOUSE_CONTROLS_SETTING = "Frisbee>ForceMouseControls";
//Add your own designs in FRISBEE_DESIGNS, be sure to put frisbee in the URL if you want others to be able to catch it without having a copy of your frisbee script.
const FRISBEE_DESIGNS = [
{"name":"Interface", "model":"http://test.thoys.nl/hifi/models/frisbee/frisbee.fbx"},
{"name":"Pizza", "model":"http://test.thoys.nl/hifi/models/frisbee/pizza.fbx"},
{"name":"Swirl", "model":"http://test.thoys.nl/hifi/models/frisbee/swirl.fbx"},
{"name":"Mayan", "model":"http://test.thoys.nl/hifi/models/frisbee/mayan.fbx"},
];
const FRISBEE_MENU_DESIGN_POSTFIX = " Design";
const FRISBEE_DESIGN_RANDOM = "Random";
const SPIN_MULTIPLIER = 1000;
const FRISBEE_LIFETIME = 300; // 5 minutes
var windowDimensions = Controller.getViewportDimensions();
var toolHeight = 50;
var toolWidth = 50;
var frisbeeToggle;
var toolBar;
var frisbeeEnabled = true;
var newfrisbeeEnabled = false;
var forceMouseControls = false;
var hydrasConnected = false;
var selectedDesign = FRISBEE_DESIGN_RANDOM;
function loadSettings() {
frisbeeEnabled = Settings.getValue(FRISBEE_ENABLED_SETTING, "true") == "true";
newfrisbeeEnabled = Settings.getValue(FRISBEE_CREATENEW_SETTING, "false") == "true";
forceMouseControls = Settings.getValue(FRISBEE_FORCE_MOUSE_CONTROLS_SETTING, "false") == "true";
selectedDesign = Settings.getValue(FRISBEE_DESIGN_SETTING, "Random");
}
function saveSettings() {
Settings.setValue(FRISBEE_ENABLED_SETTING, frisbeeEnabled ? "true" : "false");
Settings.setValue(FRISBEE_CREATENEW_SETTING, newfrisbeeEnabled ? "true" : "false");
Settings.setValue(FRISBEE_FORCE_MOUSE_CONTROLS_SETTING, forceMouseControls ? "true" : "false");
Settings.setValue(FRISBEE_DESIGN_SETTING, selectedDesign);
}
function moveOverlays() {
var newViewPort = Controller.getViewportDimensions();
if (typeof(toolBar) === 'undefined') {
initToolBar();
} else if (windowDimensions.x == newViewPort.x &&
windowDimensions.y == newViewPort.y) {
return;
}
windowDimensions = newViewPort;
var toolsX = windowDimensions.x - 8 - toolBar.width;
var toolsY = (windowDimensions.y - toolBar.height) / 2 + 80;
toolBar.move(toolsX, toolsY);
}
function frisbeeURL() {
return selectedDesign == FRISBEE_DESIGN_RANDOM ? FRISBEE_DESIGNS[Math.floor(Math.random() * FRISBEE_DESIGNS.length)].model : getFrisbee(selectedDesign).model;
}
//This function checks if the modelURL is inside of our Designs or contains "frisbee" in it.
function validFrisbeeURL(frisbeeURL) {
for (var frisbee in FRISBEE_DESIGNS) {
if (FRISBEE_DESIGNS[frisbee].model == frisbeeURL) {
return true;
}
}
return frisbeeURL.toLowerCase().indexOf("frisbee") !== -1;
}
function getFrisbee(frisbeeName) {
for (var frisbee in FRISBEE_DESIGNS) {
if (FRISBEE_DESIGNS[frisbee].name == frisbeeName) {
return FRISBEE_DESIGNS[frisbee];
}
}
return undefined;
}
function Hand(name, palm, tip, forwardButton, button3, trigger) {
this.name = name;
this.palm = palm;
this.tip = tip;
this.forwardButton = forwardButton;
this.button3 = button3;
this.trigger = trigger;
this.holdingFrisbee = false;
this.particle = false;
this.palmPosition = function() { return Controller.getSpatialControlPosition(this.palm); }
this.grabButtonPressed = function() {
return (
Controller.isButtonPressed(this.forwardButton) ||
Controller.isButtonPressed(this.button3) ||
Controller.getTriggerValue(this.trigger) > 0.5
)
};
this.holdPosition = function() { return this.palm == LEFT_PALM ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); };
this.holdRotation = function() {
var q = Controller.getSpatialControlRawRotation(this.palm);
q = Quat.multiply(MyAvatar.orientation, q);
return {x: q.x, y: q.y, z: q.z, w: q.w};
};
this.tipVelocity = function() { return Controller.getSpatialControlVelocity(this.tip); };
}
function MouseControl(button) {
this.button = button;
}
var leftHand = new Hand("LEFT", LEFT_PALM, LEFT_TIP, LEFT_BUTTON_FWD, LEFT_BUTTON_3, 0);
var rightHand = new Hand("RIGHT", RIGHT_PALM, RIGHT_TIP, RIGHT_BUTTON_FWD, RIGHT_BUTTON_3, 1);
var leftMouseControl = new MouseControl("LEFT");
var middleMouseControl = new MouseControl("MIDDLE");
var rightMouseControl = new MouseControl("RIGHT");
var mouseControls = [leftMouseControl, middleMouseControl, rightMouseControl];
var currentMouseControl = false;
var newSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
var throwSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw");
var simulatedFrisbees = [];
var wantDebugging = false;
function debugPrint(message) {
if (wantDebugging) {
print(message);
}
}
function playSound(sound, position) {
if (!SOUNDS_ENABLED) {
return;
}
var options = new AudioInjectionOptions();
options.position = position;
options.volume = 1.0;
Audio.playSound(sound, options);
}
function cleanupFrisbees() {
simulatedFrisbees = [];
var particles = Particles.findParticles(MyAvatar.position, 1000);
for (particle in particles) {
Particles.deleteParticle(particles[particle]);
}
}
function checkControllerSide(hand) {
// If I don't currently have a frisbee in my hand, then try to catch closest one
if (!hand.holdingFrisbee && hand.grabButtonPressed()) {
var closestParticle = Particles.findClosestParticle(hand.palmPosition(), CATCH_RADIUS);
var modelUrl = Particles.getParticleProperties(closestParticle).modelURL;
if (closestParticle.isKnownID && validFrisbeeURL(Particles.getParticleProperties(closestParticle).modelURL)) {
Particles.editParticle(closestParticle, {modelScale: 1, inHand: true, position: hand.holdPosition(), shouldDie: true});
Particles.deleteParticle(closestParticle);
debugPrint(hand.message + " HAND- CAUGHT SOMETHING!!");
var properties = {
position: hand.holdPosition(),
velocity: { x: 0, y: 0, z: 0},
gravity: { x: 0, y: 0, z: 0},
inHand: true,
radius: FRISBEE_RADIUS,
damping: 0.999,
modelURL: modelUrl,
modelScale: FRISBEE_MODEL_SCALE,
modelRotation: hand.holdRotation(),
lifetime: FRISBEE_LIFETIME
};
newParticle = Particles.addParticle(properties);
hand.holdingFrisbee = true;
hand.particle = newParticle;
playSound(catchSound, hand.holdPosition());
return; // exit early
}
}
// If '3' is pressed, and not holding a frisbee, make a new one
if (hand.grabButtonPressed() && !hand.holdingFrisbee && newfrisbeeEnabled) {
var properties = {
position: hand.holdPosition(),
velocity: { x: 0, y: 0, z: 0},
gravity: { x: 0, y: 0, z: 0},
inHand: true,
radius: FRISBEE_RADIUS,
damping: 0.999,
modelURL: frisbeeURL(),
modelScale: FRISBEE_MODEL_SCALE,
modelRotation: hand.holdRotation(),
lifetime: FRISBEE_LIFETIME
};
newParticle = Particles.addParticle(properties);
hand.holdingFrisbee = true;
hand.particle = newParticle;
// Play a new frisbee sound
playSound(newSound, hand.holdPosition());
return; // exit early
}
if (hand.holdingFrisbee) {
// If holding the frisbee keep it in the palm
if (hand.grabButtonPressed()) {
debugPrint(">>>>> " + hand.name + "-FRISBEE IN HAND, grabbing, hold and move");
var properties = {
position: hand.holdPosition(),
modelRotation: hand.holdRotation()
};
Particles.editParticle(hand.particle, properties);
} else {
debugPrint(">>>>> " + hand.name + "-FRISBEE IN HAND, not grabbing, THROW!!!");
// If frisbee just released, add velocity to it!
var properties = {
velocity: Vec3.multiply(hand.tipVelocity(), THROWN_VELOCITY_SCALING),
inHand: false,
lifetime: FRISBEE_LIFETIME,
gravity: { x: 0, y: -GRAVITY_STRENGTH, z: 0},
modelRotation: hand.holdRotation()
};
Particles.editParticle(hand.particle, properties);
simulatedFrisbees.push(hand.particle);
hand.holdingFrisbee = false;
hand.particle = false;
playSound(throwSound, hand.holdPosition());
}
}
}
function initToolBar() {
toolBar = new ToolBar(0, 0, ToolBar.VERTICAL);
frisbeeToggle = toolBar.addTool({
imageURL: FRISBEE_BUTTON_URL,
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
visible: true,
alpha: 0.9
}, true);
enableNewFrisbee(newfrisbeeEnabled);
}
function hydraCheck() {
var numberOfButtons = Controller.getNumberOfButtons();
var numberOfTriggers = Controller.getNumberOfTriggers();
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
hydrasConnected = (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2);
return hydrasConnected;
}
function checkController(deltaTime) {
moveOverlays();
if (!frisbeeEnabled) {
return;
}
// this is expected for hydras
if (hydraCheck()) {
checkControllerSide(leftHand);
checkControllerSide(rightHand);
}
if (!hydrasConnected || forceMouseControls) {
//TODO: add mouse cursor control code here.
}
}
function controlFrisbees(deltaTime) {
var killSimulations = [];
for (frisbee in simulatedFrisbees) {
var properties = Particles.getParticleProperties(simulatedFrisbees[frisbee]);
//get the horizon length from the velocity origin in order to get speed
var speed = Vec3.length({x:properties.velocity.x, y:0, z:properties.velocity.z});
if (speed < MIN_SIMULATION_SPEED) {
//kill the frisbee simulation when speed is low
killSimulations.push(frisbee);
continue;
}
Particles.editParticle(simulatedFrisbees[frisbee], {modelRotation: Quat.multiply(properties.modelRotation, Quat.fromPitchYawRollDegrees(0, speed * deltaTime * SPIN_MULTIPLIER, 0))});
}
for (var i = killSimulations.length - 1; i >= 0; i--) {
simulatedFrisbees.splice(killSimulations[i], 1);
}
}
//catches interfering calls of hydra-cursors
function withinBounds(coords) {
return coords.x >= 0 && coords.x < windowDimensions.x && coords.y >= 0 && coords.y < windowDimensions.y;
}
function mouseMoveEvent(event) {
//TODO: mouse controls //print(withinBounds(event)); //print("move"+event.x);
}
function mousePressEvent(event) {
print(event.x);
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (frisbeeToggle == toolBar.clicked(clickedOverlay)) {
newfrisbeeEnabled = !newfrisbeeEnabled;
saveSettings();
enableNewFrisbee(newfrisbeeEnabled);
}
}
function enableNewFrisbee(enable) {
if (toolBar.numberOfTools() > 0) {
toolBar.tools[0].select(enable);
}
}
function mouseReleaseEvent(event) {
//TODO: mouse controls //print(JSON.stringify(event));
}
function setupMenus() {
Menu.addMenu(FRISBEE_MENU);
Menu.addMenuItem({
menuName: FRISBEE_MENU,
menuItemName: "Frisbee Enabled",
isCheckable: true,
isChecked: frisbeeEnabled
});
Menu.addMenuItem({
menuName: FRISBEE_MENU,
menuItemName: "Cleanup Frisbees"
});
Menu.addMenuItem({
menuName: FRISBEE_MENU,
menuItemName: "Force Mouse Controls",
isCheckable: true,
isChecked: forceMouseControls
});
Menu.addMenu(FRISBEE_DESIGN_MENU);
Menu.addMenuItem({
menuName: FRISBEE_DESIGN_MENU,
menuItemName: FRISBEE_DESIGN_RANDOM + FRISBEE_MENU_DESIGN_POSTFIX,
isCheckable: true,
isChecked: selectedDesign == FRISBEE_DESIGN_RANDOM
});
for (frisbee in FRISBEE_DESIGNS) {
Menu.addMenuItem({
menuName: FRISBEE_DESIGN_MENU,
menuItemName: FRISBEE_DESIGNS[frisbee].name + FRISBEE_MENU_DESIGN_POSTFIX,
isCheckable: true,
isChecked: selectedDesign == FRISBEE_DESIGNS[frisbee].name
});
}
}
//startup calls:
loadSettings();
setupMenus();
function scriptEnding() {
toolBar.cleanup();
Menu.removeMenu(FRISBEE_MENU);
}
function menuItemEvent(menuItem) {
if (menuItem == "Cleanup Frisbees") {
cleanupFrisbees();
return;
} else if (menuItem == "Frisbee Enabled") {
frisbeeEnabled = Menu.isOptionChecked(menuItem);
saveSettings();
return;
} else if (menuItem == "Force Mouse Controls") {
forceMouseControls = Menu.isOptionChecked(menuItem);
saveSettings();
return;
}
if (menuItem.indexOf(FRISBEE_MENU_DESIGN_POSTFIX, menuItem.length - FRISBEE_MENU_DESIGN_POSTFIX.length) !== -1) {
var item_name = menuItem.substring(0, menuItem.length - FRISBEE_MENU_DESIGN_POSTFIX.length);
if (item_name == FRISBEE_DESIGN_RANDOM || getFrisbee(item_name) != undefined) {
Menu.setIsOptionChecked(selectedDesign + FRISBEE_MENU_DESIGN_POSTFIX, false);
selectedDesign = item_name;
saveSettings();
Menu.setIsOptionChecked(selectedDesign + FRISBEE_MENU_DESIGN_POSTFIX, true);
}
}
}
// register the call back so it fires before each data send
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Menu.menuItemEvent.connect(menuItemEvent);
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(checkController);
Script.update.connect(controlFrisbees);

View file

@ -21,10 +21,10 @@ var position = { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.pos
var joysticksCaptured = false;
var THRUST_CONTROLLER = 0;
var VIEW_CONTROLLER = 1;
var INITIAL_THRUST_MULTPLIER = 1.0;
var INITIAL_THRUST_MULTIPLIER = 1.0;
var THRUST_INCREASE_RATE = 1.05;
var MAX_THRUST_MULTIPLIER = 75.0;
var thrustMultiplier = INITIAL_THRUST_MULTPLIER;
var thrustMultiplier = INITIAL_THRUST_MULTIPLIER;
var grabDelta = { x: 0, y: 0, z: 0};
var grabStartPosition = { x: 0, y: 0, z: 0};
var grabDeltaVelocity = { x: 0, y: 0, z: 0};
@ -34,6 +34,8 @@ var grabbingWithRightHand = false;
var wasGrabbingWithRightHand = false;
var grabbingWithLeftHand = false;
var wasGrabbingWithLeftHand = false;
var movingWithHead = false;
var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw;
var EPSILON = 0.000001;
var velocity = { x: 0, y: 0, z: 0};
var THRUST_MAG_UP = 100.0;
@ -241,6 +243,47 @@ function handleGrabBehavior(deltaTime) {
wasGrabbingWithLeftHand = grabbingWithLeftHand;
}
var HEAD_MOVE_DEAD_ZONE = 0.0;
var HEAD_STRAFE_DEAD_ZONE = 0.0;
var HEAD_ROTATE_DEAD_ZONE = 0.0;
var HEAD_THRUST_FWD_SCALE = 12000.0;
var HEAD_THRUST_STRAFE_SCALE = 1000.0;
var HEAD_YAW_RATE = 2.0;
var HEAD_PITCH_RATE = 1.0;
var HEAD_ROLL_THRUST_SCALE = 75.0;
var HEAD_PITCH_LIFT_THRUST = 3.0;
function moveWithHead(deltaTime) {
if (movingWithHead) {
var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw;
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
var bodyLocalCurrentHeadVector = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.angleAxis(-deltaYaw, {x:0, y: 1, z:0}), bodyLocalCurrentHeadVector);
var headDelta = Vec3.subtract(bodyLocalCurrentHeadVector, headStartPosition);
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion
// Thrust based on leaning forward and side-to-side
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_FWD_SCALE * deltaTime));
}
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_STRAFE_SCALE * deltaTime));
}
if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) {
var orientation = Quat.multiply(Quat.angleAxis(deltaYaw * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation);
MyAvatar.orientation = orientation;
}
// Thrust Up/Down based on head pitch
MyAvatar.addThrust(Vec3.multiply({ x:0, y:1, z:0 }, (MyAvatar.getHeadFinalPitch() - headStartFinalPitch) * HEAD_PITCH_LIFT_THRUST * deltaTime));
// For head trackers, adjust pitch by head pitch
MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime;
// Thrust strafe based on roll ange
MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), -(MyAvatar.getHeadFinalRoll() - headStartRoll) * HEAD_ROLL_THRUST_SCALE * deltaTime));
}
}
// Update for joysticks and move button
function flyWithHydra(deltaTime) {
var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
@ -262,7 +305,7 @@ function flyWithHydra(deltaTime) {
thrustJoystickPosition.x * thrustMultiplier * deltaTime);
MyAvatar.addThrust(thrustRight);
} else {
thrustMultiplier = INITIAL_THRUST_MULTPLIER;
thrustMultiplier = INITIAL_THRUST_MULTIPLIER;
}
// View Controller
@ -280,6 +323,7 @@ function flyWithHydra(deltaTime) {
MyAvatar.headPitch = newPitch;
}
handleGrabBehavior(deltaTime);
moveWithHead(deltaTime);
displayDebug();
}
@ -296,3 +340,19 @@ function scriptEnding() {
}
Script.scriptEnding.connect(scriptEnding);
Controller.keyPressEvent.connect(function(event) {
if (event.text == "SPACE" && !movingWithHead) {
movingWithHead = true;
headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
headStartRoll = MyAvatar.getHeadFinalRoll();
headStartYaw = MyAvatar.getHeadFinalYaw();
}
});
Controller.keyReleaseEvent.connect(function(event) {
if (event.text == "SPACE") {
movingWithHead = false;
}
});

201
examples/speechControl.js Normal file
View file

@ -0,0 +1,201 @@
//
// speechControl.js
// examples
//
// Created by Ryan Huffman on 07/31/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 ACCELERATION = 80;
var STEP_DURATION = 1.0; // Duration of a step command in seconds
var TURN_DEGREES = 90;
var SLIGHT_TURN_DEGREES = 45;
var TURN_AROUND_DEGREES = 180;
var TURN_RATE = 90; // Turn rate in degrees per second
/*****************************************************************************/
/** COMMANDS *****************************************************************/
var CMD_MOVE_FORWARD = "Move forward";
var CMD_MOVE_BACKWARD = "Move backward";
var CMD_MOVE_UP = "Move up";
var CMD_MOVE_DOWN = "Move down";
var CMD_MOVE_LEFT = "Move left";
var CMD_MOVE_RIGHT = "Move right";
var CMD_STEP_FORWARD = "Step forward";
var CMD_STEP_BACKWARD = "Step backward";
var CMD_STEP_LEFT = "Step left";
var CMD_STEP_RIGHT = "Step right";
var CMD_STEP_UP = "Step up";
var CMD_STEP_DOWN = "Step down";
var CMD_TURN_LEFT = "Turn left";
var CMD_TURN_SLIGHT_LEFT = "Turn slight left";
var CMD_TURN_RIGHT = "Turn right";
var CMD_TURN_SLIGHT_RIGHT = "Turn slight right";
var CMD_TURN_AROUND = "Turn around";
var CMD_STOP = "Stop";
var CMD_SHOW_COMMANDS = "Show commands";
var MOVE_COMMANDS = [
CMD_MOVE_FORWARD,
CMD_MOVE_BACKWARD,
CMD_MOVE_UP,
CMD_MOVE_DOWN,
CMD_MOVE_LEFT,
CMD_MOVE_RIGHT,
];
var STEP_COMMANDS = [
CMD_STEP_FORWARD,
CMD_STEP_BACKWARD,
CMD_STEP_UP,
CMD_STEP_DOWN,
CMD_STEP_LEFT,
CMD_STEP_RIGHT,
];
var TURN_COMMANDS = [
CMD_TURN_LEFT,
CMD_TURN_SLIGHT_LEFT,
CMD_TURN_RIGHT,
CMD_TURN_SLIGHT_RIGHT,
CMD_TURN_AROUND,
];
var OTHER_COMMANDS = [
CMD_STOP,
CMD_SHOW_COMMANDS,
];
var ALL_COMMANDS = []
.concat(MOVE_COMMANDS)
.concat(STEP_COMMANDS)
.concat(TURN_COMMANDS)
.concat(OTHER_COMMANDS);
/** END OF COMMANDS **********************************************************/
/*****************************************************************************/
var currentCommandFunc = null;
function handleCommandRecognized(command) {
if (MOVE_COMMANDS.indexOf(command) > -1 || STEP_COMMANDS.indexOf(command) > -1) {
// If this is a STEP_* command, we will want to countdown the duration
// of time to move. MOVE_* commands don't stop.
var timeRemaining = MOVE_COMMANDS.indexOf(command) > -1 ? 0 : STEP_DURATION;
var accel = { x: 0, y: 0, z: 0 };
if (command == CMD_MOVE_FORWARD || command == CMD_STEP_FORWARD) {
accel = { x: 0, y: 0, z: 1 };
} else if (command == CMD_MOVE_BACKWARD || command == CMD_STEP_BACKWARD) {
accel = { x: 0, y: 0, z: -1 };
} else if (command === CMD_MOVE_UP || command == CMD_STEP_UP) {
accel = { x: 0, y: 1, z: 0 };
} else if (command == CMD_MOVE_DOWN || command == CMD_STEP_DOWN) {
accel = { x: 0, y: -1, z: 0 };
} else if (command == CMD_MOVE_LEFT || command == CMD_STEP_LEFT) {
accel = { x: -1, y: 0, z: 0 };
} else if (command == CMD_MOVE_RIGHT || command == CMD_STEP_RIGHT) {
accel = { x: 1, y: 0, z: 0 };
}
currentCommandFunc = function(dt) {
if (timeRemaining > 0 && dt >= timeRemaining) {
dt = timeRemaining;
}
var headOrientation = MyAvatar.headOrientation;
var front = Quat.getFront(headOrientation);
var right = Quat.getRight(headOrientation);
var up = Quat.getUp(headOrientation);
var thrust = Vec3.multiply(front, accel.z * ACCELERATION);
thrust = Vec3.sum(thrust, Vec3.multiply(right, accel.x * ACCELERATION));
thrust = Vec3.sum(thrust, Vec3.multiply(up, accel.y * ACCELERATION));
MyAvatar.addThrust(thrust);
if (timeRemaining > 0) {
timeRemaining -= dt;
return timeRemaining > 0;
}
return true;
};
} else if (TURN_COMMANDS.indexOf(command) > -1) {
var degreesRemaining;
var sign;
if (command == CMD_TURN_LEFT) {
sign = 1;
degreesRemaining = TURN_DEGREES;
} else if (command == CMD_TURN_RIGHT) {
sign = -1;
degreesRemaining = TURN_DEGREES;
} else if (command == CMD_TURN_SLIGHT_LEFT) {
sign = 1;
degreesRemaining = SLIGHT_TURN_DEGREES;
} else if (command == CMD_TURN_SLIGHT_RIGHT) {
sign = -1;
degreesRemaining = SLIGHT_TURN_DEGREES;
} else if (command == CMD_TURN_AROUND) {
sign = 1;
degreesRemaining = TURN_AROUND_DEGREES;
}
currentCommandFunc = function(dt) {
// Determine how much to turn by
var turnAmount = TURN_RATE * dt;
if (turnAmount > degreesRemaining) {
turnAmount = degreesRemaining;
}
// Apply turn
var orientation = MyAvatar.orientation;
var deltaOrientation = Quat.fromPitchYawRollDegrees(0, sign * turnAmount, 0);
MyAvatar.orientation = Quat.multiply(orientation, deltaOrientation);
degreesRemaining -= turnAmount;
return turnAmount > 0;
}
} else if (command == CMD_STOP) {
currentCommandFunc = null;
} else if (command == CMD_SHOW_COMMANDS) {
var msg = "";
for (var i = 0; i < ALL_COMMANDS.length; i++) {
msg += ALL_COMMANDS[i] + "\n";
}
Window.alert(msg);
}
}
function update(dt) {
if (currentCommandFunc) {
if (currentCommandFunc(dt) === false) {
currentCommandFunc = null;
}
}
}
function setup() {
for (var i = 0; i < ALL_COMMANDS.length; i++) {
SpeechRecognizer.addCommand(ALL_COMMANDS[i]);
}
}
function scriptEnding() {
for (var i = 0; i < ALL_COMMANDS.length; i++) {
SpeechRecognizer.removeCommand(ALL_COMMANDS[i]);
}
}
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(update);
SpeechRecognizer.commandRecognized.connect(handleCommandRecognized);
setup();

View file

@ -132,20 +132,34 @@ ToolBar = function(x, y, direction) {
this.y = y;
this.width = 0;
this.height = 0;
this.back = this.back = Overlays.addOverlay("text", {
backgroundColor: { red: 255, green: 255, blue: 255 },
x: this.x,
y: this.y,
width: this.width,
height: this.height,
alpha: 1.0,
visible: false
});
this.addTool = function(properties, selectable, selected) {
if (direction == ToolBar.HORIZONTAL) {
properties.x = this.x + this.width;
properties.y = this.y;
this.width += properties.width + ToolBar.SPACING;
this.height += Math.max(properties.height, this.height);
this.height = Math.max(properties.height, this.height);
} else {
properties.x = this.x;
properties.y = this.y + this.height;
this.width = Math.max(properties.width, this.width);
this.height += properties.height + ToolBar.SPACING;
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
width: this.width + 2 * ToolBar.SPACING,
height: this.height + 2 * ToolBar.SPACING
});
}
this.tools[this.tools.length] = new Tool(properties, selectable, selected);
return ((this.tools.length) - 1);
@ -159,18 +173,48 @@ ToolBar = function(x, y, direction) {
for(var tool in this.tools) {
this.tools[tool].move(this.tools[tool].x() + dx, this.tools[tool].y() + dy);
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
x: x - ToolBar.SPACING,
y: y - ToolBar.SPACING
});
}
}
this.setAlpha = function(alpha) {
for(var tool in this.tools) {
this.setAlpha = function(alpha, tool) {
if(typeof(tool) === 'undefined') {
for(var tool in this.tools) {
this.tools[tool].setAlpha(alpha);
}
if (this.back != null) {
Overlays.editOverlay(this.back, { alpha: alpha});
}
} else {
this.tools[tool].setAlpha(alpha);
}
}
this.setBack = function(color, alpha) {
if (color == null) {
Overlays.editOverlay(this.back, {
visible: false
});
} else {
Overlays.editOverlay(this.back, {
visible: true,
backgroundColor: color,
alpha: alpha
})
}
}
this.show = function(doShow) {
for(var tool in this.tools) {
this.tools[tool].show(doShow);
}
if (this.back != null) {
Overlays.editOverlay(this.back, { visible: doShow});
}
}
this.clicked = function(clickedOverlay) {
@ -200,6 +244,11 @@ ToolBar = function(x, y, direction) {
delete this.tools[tool];
}
if (this.back != null) {
Overlays.deleteOverlay(this.back);
this.back = null;
}
this.tools = [];
this.x = x;
this.y = y;

View file

@ -46,6 +46,15 @@ foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels pa
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}")
endforeach(SUBDIR)
# Add SpeechRecognizer if on OS X, otherwise remove
if (APPLE)
file(GLOB INTERFACE_OBJCPP_SRCS "src/SpeechRecognizer.mm")
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${INTERFACE_OBJCPP_SRCS})
else ()
get_filename_component(SPEECHRECOGNIZER_H "src/SpeechRecognizer.h" ABSOLUTE)
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_H})
endif ()
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Script Svg WebKitWidgets)
# grab the ui files in resources/ui
@ -165,8 +174,9 @@ if (APPLE)
find_library(CoreFoundation CoreFoundation)
find_library(GLUT GLUT)
find_library(OpenGL OpenGL)
find_library(AppKit AppKit)
target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation} ${GLUT} ${OpenGL})
target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation} ${GLUT} ${OpenGL} ${AppKit})
# install command for OS X bundle
INSTALL(TARGETS ${TARGET_NAME}

View file

@ -894,7 +894,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
break;
case Qt::Key_Space:
case Qt::Key_Apostrophe:
resetSensors();
break;
@ -1051,20 +1051,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_R:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode);
} else if (isMeta) {
if (_myAvatar->isRecording()) {
_myAvatar->stopRecording();
} else {
_myAvatar->startRecording();
_audio.setRecorder(_myAvatar->getRecorder());
}
} else {
if (_myAvatar->isPlaying()) {
_myAvatar->stopPlaying();
} else {
_myAvatar->startPlaying();
_audio.setPlayer(_myAvatar->getPlayer());
}
}
break;
case Qt::Key_Percent:
@ -3749,6 +3735,10 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
scriptEngine->registerGlobalObject("Camera", cameraScriptable);
connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater()));
#ifdef Q_OS_MAC
scriptEngine->registerGlobalObject("SpeechRecognizer", Menu::getInstance()->getSpeechRecognizer());
#endif
ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface();
scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable);
connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater()));
@ -3831,6 +3821,10 @@ void Application::stopAllScripts(bool restart) {
it.value()->stop();
qDebug() << "stopping script..." << it.key();
}
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
// whenever a script stops in case it happened to have been setting joint rotations.
// TODO: expose animation priorities and provide a layered animation control system.
_myAvatar->clearJointAnimationPriorities();
}
void Application::stopScript(const QString &scriptName) {
@ -3838,6 +3832,10 @@ void Application::stopScript(const QString &scriptName) {
if (_scriptEnginesHash.contains(scriptURLString)) {
_scriptEnginesHash.value(scriptURLString)->stop();
qDebug() << "stopping script..." << scriptName;
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
// whenever a script stops in case it happened to have been setting joint rotations.
// TODO: expose animation priorities and provide a layered animation control system.
_myAvatar->clearJointAnimationPriorities();
}
}

View file

@ -94,6 +94,9 @@ Menu::Menu() :
_octreeStatsDialog(NULL),
_lodToolsDialog(NULL),
_userLocationsDialog(NULL),
#ifdef Q_OS_MAC
_speechRecognizer(),
#endif
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_oculusUIAngularSize(DEFAULT_OCULUS_UI_ANGULAR_SIZE),
@ -221,19 +224,16 @@ Menu::Menu() :
addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments()));
addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations()));
addDisabledActionAndSeparator(editMenu, "Physics");
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false,
avatar, SLOT(updateMotionBehaviorsFromMenu()));
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::StandOnNearbyFloors, 0, true,
avatar, SLOT(updateMotionBehaviorsFromMenu()));
addAvatarCollisionSubMenu(editMenu);
QMenu* toolsMenu = addMenu("Tools");
addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor()));
addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, this, SLOT(showScriptEditor()));
#ifdef Q_OS_MAC
QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::ControlWithSpeech,
Qt::CTRL | Qt::SHIFT | Qt::Key_C, _speechRecognizer.getEnabled(), &_speechRecognizer, SLOT(setEnabled(bool)));
connect(&_speechRecognizer, SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool)));
#endif
#ifdef HAVE_QXMPP
_chatAction = addActionToQMenuAndActionHash(toolsMenu,
MenuOption::Chat,
@ -257,6 +257,45 @@ Menu::Menu() :
this,
SLOT(toggleConsole()));
QMenu* avatarMenu = addMenu("Avatar");
QMenu* avatarSizeMenu = avatarMenu->addMenu("Size");
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::IncreaseAvatarSize,
Qt::Key_Plus,
appInstance->getAvatar(),
SLOT(increaseSize()));
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::DecreaseAvatarSize,
Qt::Key_Minus,
appInstance->getAvatar(),
SLOT(decreaseSize()));
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::ResetAvatarSize,
Qt::Key_Equal,
appInstance->getAvatar(),
SLOT(resetSize()));
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ChatCircling, 0, false);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false,
avatar, SLOT(updateMotionBehaviorsFromMenu()));
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true,
avatar, SLOT(updateMotionBehaviorsFromMenu()));
QMenu* collisionsMenu = avatarMenu->addMenu("Collide With...");
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll);
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithAvatars,
0, true, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithVoxels,
0, false, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithParticles,
0, true, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithEnvironment,
0, false, avatar, SLOT(updateCollisionGroups()));
QMenu* viewMenu = addMenu("View");
#ifdef Q_OS_MAC
@ -304,25 +343,6 @@ Menu::Menu() :
Qt::CTRL | Qt::SHIFT | Qt::Key_3, false,
&nodeBounds, SLOT(setShowParticleNodes(bool)));
QMenu* avatarSizeMenu = viewMenu->addMenu("Avatar Size");
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::IncreaseAvatarSize,
Qt::Key_Plus,
appInstance->getAvatar(),
SLOT(increaseSize()));
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::DecreaseAvatarSize,
Qt::Key_Minus,
appInstance->getAvatar(),
SLOT(decreaseSize()));
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::ResetAvatarSize,
Qt::Key_Equal,
appInstance->getAvatar(),
SLOT(resetSize()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::OffAxisProjection, 0, false);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MoveWithLean, 0, false);
@ -338,111 +358,97 @@ Menu::Menu() :
QMenu* developerMenu = addMenu("Developer");
QMenu* renderOptionsMenu = developerMenu->addMenu("Rendering Options");
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true);
QMenu* renderOptionsMenu = developerMenu->addMenu("Render");
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Avatars, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows");
QActionGroup* shadowGroup = new QActionGroup(shadowMenu);
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::AvatarsReceiveShadows, 0, true));
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu,
MenuOption::Voxels,
Qt::SHIFT | Qt::Key_V,
true,
appInstance,
SLOT(setRenderVoxels(bool)));
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true);
addActionToQMenuAndActionHash(renderOptionsMenu,
MenuOption::GlowMode,
0,
appInstance->getGlowEffect(),
SLOT(cycleRenderMode()));
QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows");
QActionGroup* shadowGroup = new QActionGroup(shadowMenu);
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false));
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::StringHair, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options");
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu,
MenuOption::Voxels,
Qt::SHIFT | Qt::Key_V,
true,
appInstance,
SLOT(setRenderVoxels(bool)));
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableAutoAdjustLOD);
QMenu* modelOptionsMenu = developerMenu->addMenu("Model Options");
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::Models, 0, true);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AvatarsReceiveShadows, 0, true);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagdoll);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
QMenu* avatarDebugMenu = developerMenu->addMenu("Avatar");
#ifdef HAVE_FACESHIFT
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
MenuOption::Faceshift,
0,
true,
appInstance->getFaceshift(),
SLOT(setTCPEnabled(bool)));
#endif
#ifdef HAVE_FACEPLUS
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Faceplus, 0, true,
appInstance->getFaceplus(), SLOT(updateEnabled()));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Faceplus, 0, true,
appInstance->getFaceplus(), SLOT(updateEnabled()));
#endif
#ifdef HAVE_VISAGE
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, false,
appInstance->getVisage(), SLOT(updateEnabled()));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Visage, 0, false,
appInstance->getVisage(), SLOT(updateEnabled()));
#endif
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::FocusIndicators, 0, false);
QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options");
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true);
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseLasers, 0, true);
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
addCheckableActionToQMenuAndActionHash(handOptionsMenu,
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSkeletonCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
QMenu* modelDebugMenu = developerMenu->addMenu("Models");
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels");
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableAutoAdjustLOD);
QMenu* handOptionsMenu = developerMenu->addMenu("Hands");
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
QMenu* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense");
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
MenuOption::FilterSixense,
0,
true,
appInstance->getSixenseManager(),
SLOT(setFilter(bool)));
addCheckableActionToQMenuAndActionHash(handOptionsMenu,
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
MenuOption::LowVelocityFilter,
0,
true,
appInstance,
SLOT(setLowVelocityFilter(bool)));
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true);
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseLasers, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false);
addCheckableActionToQMenuAndActionHash(developerMenu,
QMenu* networkMenu = developerMenu->addMenu("Network");
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false);
addCheckableActionToQMenuAndActionHash(networkMenu,
MenuOption::DisableActivityLogger,
0,
false,
@ -451,9 +457,7 @@ Menu::Menu() :
addActionToQMenuAndActionHash(developerMenu, MenuOption::WalletPrivateKey, 0, this, SLOT(changePrivateKey()));
addDisabledActionAndSeparator(developerMenu, "Testing");
QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools");
QMenu* timingMenu = developerMenu->addMenu("Timing and Stats");
QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer");
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false);
@ -465,8 +469,10 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::TestPing, 0, true);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer);
addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, this, SLOT(runTests()));
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings);
QMenu* frustumMenu = developerMenu->addMenu("View Frustum Debugging Tools");
QMenu* frustumMenu = developerMenu->addMenu("View Frustum");
addCheckableActionToQMenuAndActionHash(frustumMenu, MenuOption::DisplayFrustum, Qt::SHIFT | Qt::Key_F);
addActionToQMenuAndActionHash(frustumMenu,
MenuOption::FrustumRenderMode,
@ -476,11 +482,7 @@ Menu::Menu() :
updateFrustumRenderModeAction();
QMenu* renderDebugMenu = developerMenu->addMenu("Render Debugging Tools");
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings);
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings);
QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools");
QMenu* audioDebugMenu = developerMenu->addMenu("Audio");
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction,
0,
true,
@ -493,7 +495,7 @@ Menu::Menu() :
appInstance->getAudio(),
SLOT(toggleAudioFilter()));
QMenu* audioFilterMenu = audioDebugMenu->addMenu("Audio Filter Options");
QMenu* audioFilterMenu = audioDebugMenu->addMenu("Audio Filter");
addDisabledActionAndSeparator(audioFilterMenu, "Filter Response");
{
QAction *flat = addCheckableActionToQMenuAndActionHash(audioFilterMenu, MenuOption::AudioFilterFlat,
@ -557,7 +559,7 @@ Menu::Menu() :
appInstance->getAudio(),
SLOT(toggleScopePause()));
QMenu* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope Options");
QMenu* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope");
addDisabledActionAndSeparator(audioScopeMenu, "Display Frames");
{
QAction *fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames,
@ -648,13 +650,17 @@ Menu::Menu() :
appInstance->getAudio(),
SLOT(toggleStatsShowInjectedStreams()));
connect(appInstance->getAudio(), SIGNAL(muteToggled()), this, SLOT(audioMuteToggled()));
QMenu* experimentalOptionsMenu = developerMenu->addMenu("Experimental");
addCheckableActionToQMenuAndActionHash(experimentalOptionsMenu, MenuOption::BuckyBalls, 0, false);
addCheckableActionToQMenuAndActionHash(experimentalOptionsMenu, MenuOption::StringHair, 0, false);
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
this,
SLOT(pasteToVoxel()));
connect(appInstance->getAudio(), SIGNAL(muteToggled()), this, SLOT(audioMuteToggled()));
#ifndef Q_OS_MAC
QMenu* helpMenu = addMenu("Help");
QAction* helpAction = helpMenu->addAction(MenuOption::AboutApp);
@ -692,6 +698,10 @@ void Menu::loadSettings(QSettings* settings) {
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString();
setScriptsLocation(settings->value("scriptsLocation", QString()).toString());
#ifdef Q_OS_MAC
_speechRecognizer.setEnabled(settings->value("speechRecognitionEnabled", false).toBool());
#endif
settings->beginGroup("View Frustum Offset Camera");
// in case settings is corrupt or missing loadSetting() will check for NaN
_viewFrustumOffset.yaw = loadSetting(settings, "viewFrustumOffsetYaw", 0.0f);
@ -739,6 +749,9 @@ void Menu::saveSettings(QSettings* settings) {
settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust);
settings->setValue("snapshotsLocation", _snapshotsLocation);
settings->setValue("scriptsLocation", _scriptsLocation);
#ifdef Q_OS_MAC
settings->setValue("speechRecognitionEnabled", _speechRecognizer.getEnabled());
#endif
settings->beginGroup("View Frustum Offset Camera");
settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw);
settings->setValue("viewFrustumOffsetPitch", _viewFrustumOffset.pitch);
@ -1457,7 +1470,9 @@ void Menu::toggleConsole() {
void Menu::audioMuteToggled() {
QAction *muteAction = _actionHash.value(MenuOption::MuteAudio);
muteAction->setChecked(Application::getInstance()->getAudio()->getMuted());
if (muteAction) {
muteAction->setChecked(Application::getInstance()->getAudio()->getMuted());
}
}
void Menu::bandwidthDetailsClosed() {
@ -1629,45 +1644,31 @@ void Menu::runTests() {
void Menu::updateFrustumRenderModeAction() {
QAction* frustumRenderModeAction = _actionHash.value(MenuOption::FrustumRenderMode);
switch (_frustumDrawMode) {
default:
case FRUSTUM_DRAW_MODE_ALL:
frustumRenderModeAction->setText("Render Mode - All");
break;
case FRUSTUM_DRAW_MODE_VECTORS:
frustumRenderModeAction->setText("Render Mode - Vectors");
break;
case FRUSTUM_DRAW_MODE_PLANES:
frustumRenderModeAction->setText("Render Mode - Planes");
break;
case FRUSTUM_DRAW_MODE_NEAR_PLANE:
frustumRenderModeAction->setText("Render Mode - Near");
break;
case FRUSTUM_DRAW_MODE_FAR_PLANE:
frustumRenderModeAction->setText("Render Mode - Far");
break;
case FRUSTUM_DRAW_MODE_KEYHOLE:
frustumRenderModeAction->setText("Render Mode - Keyhole");
break;
if (frustumRenderModeAction) {
switch (_frustumDrawMode) {
default:
case FRUSTUM_DRAW_MODE_ALL:
frustumRenderModeAction->setText("Render Mode - All");
break;
case FRUSTUM_DRAW_MODE_VECTORS:
frustumRenderModeAction->setText("Render Mode - Vectors");
break;
case FRUSTUM_DRAW_MODE_PLANES:
frustumRenderModeAction->setText("Render Mode - Planes");
break;
case FRUSTUM_DRAW_MODE_NEAR_PLANE:
frustumRenderModeAction->setText("Render Mode - Near");
break;
case FRUSTUM_DRAW_MODE_FAR_PLANE:
frustumRenderModeAction->setText("Render Mode - Far");
break;
case FRUSTUM_DRAW_MODE_KEYHOLE:
frustumRenderModeAction->setText("Render Mode - Keyhole");
break;
}
}
}
void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) {
// add avatar collisions subMenu to overMenu
QMenu* subMenu = overMenu->addMenu("Collision Options");
Application* appInstance = Application::getInstance();
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment,
0, false, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars,
0, true, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels,
0, false, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles,
0, true, avatar, SLOT(updateCollisionGroups()));
}
QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) {
QList<QAction*> menuActions;
if (menu) {
@ -1887,10 +1888,9 @@ void Menu::removeMenuItem(const QString& menu, const QString& menuitem) {
};
bool Menu::menuItemExists(const QString& menu, const QString& menuitem) {
QMenu* menuObj = getMenu(menu);
QAction* menuItemAction = _actionHash.value(menuitem);
if (menuObj && menuItemAction) {
return true;
if (menuItemAction) {
return (getMenu(menu) != NULL);
}
return false;
};

View file

@ -23,6 +23,10 @@
#include <MenuItemProperties.h>
#include <OctreeConstants.h>
#ifdef Q_OS_MAC
#include "SpeechRecognizer.h"
#endif
#include "location/LocationManager.h"
#include "ui/PreferencesDialog.h"
#include "ui/ChatWindow.h"
@ -137,6 +141,10 @@ public:
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
#ifdef Q_OS_MAC
SpeechRecognizer* getSpeechRecognizer() { return &_speechRecognizer; }
#endif
// User Tweakable PPS from Voxel Server
int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; }
void setMaxVoxelPacketsPerSecond(int maxVoxelPacketsPerSecond) { _maxVoxelPacketsPerSecond = maxVoxelPacketsPerSecond; }
@ -246,8 +254,6 @@ private:
void updateFrustumRenderModeAction();
void addAvatarCollisionSubMenu(QMenu* overMenu);
QAction* getActionFromName(const QString& menuName, QMenu* menu);
QMenu* getSubMenuFromName(const QString& menuName, QMenu* menu);
QMenu* getMenuParent(const QString& menuName, QString& finalMenuPart);
@ -274,6 +280,9 @@ private:
OctreeStatsDialog* _octreeStatsDialog;
LodToolsDialog* _lodToolsDialog;
UserLocationsDialog* _userLocationsDialog;
#ifdef Q_OS_MAC
SpeechRecognizer _speechRecognizer;
#endif
int _maxVoxels;
float _voxelSizeScale;
float _oculusUIAngularSize;
@ -342,25 +351,27 @@ namespace MenuOption {
const QString AvatarsReceiveShadows = "Avatars Receive Shadows";
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";
const QString BlueSpeechSphere = "Blue Sphere While Speaking";
const QString BuckyBalls = "Bucky Balls";
const QString CascadedShadows = "Cascaded";
const QString Chat = "Chat...";
const QString ChatCircling = "Chat Circling";
const QString CollideAsRagdoll = "Collide As Ragdoll";
const QString CollideWithAvatars = "Collide With Avatars";
const QString CollideAsRagdoll = "Collide With Self (Ragdoll)";
const QString CollideWithAvatars = "Collide With Other Avatars";
const QString CollideWithEnvironment = "Collide With World Boundaries";
const QString CollideWithParticles = "Collide With Particles";
const QString CollideWithVoxels = "Collide With Voxels";
const QString Collisions = "Collisions";
const QString Console = "Console...";
const QString ControlWithSpeech = "Control With Speech";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DisableActivityLogger = "Disable Activity Logger";
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisableNackPackets = "Disable NACK Packets";
const QString DisplayFrustum = "Display Frustum";
const QString DisplayHands = "Display Hands";
const QString DisplayHandTargets = "Display Hand Targets";
const QString DisplayHands = "Show Hand Info";
const QString DisplayHandTargets = "Show Hand Targets";
const QString DisplayModelBounds = "Display Model Bounds";
const QString DisplayModelElementChildProxies = "Display Model Element Children";
const QString DisplayModelElementProxy = "Display Model Element Bounds";
@ -380,7 +391,6 @@ namespace MenuOption {
const QString Faceshift = "Faceshift";
const QString FilterSixense = "Smooth Sixense Movement";
const QString FirstPerson = "First Person";
const QString FocusIndicators = "Focus Indicators";
const QString FrameTimer = "Show Timer";
const QString FrustumRenderMode = "Render Mode";
const QString Fullscreen = "Fullscreen";
@ -391,7 +401,6 @@ namespace MenuOption {
const QString GoToDomain = "Go To Domain...";
const QString GoTo = "Go To...";
const QString GoToLocation = "Go To Location...";
const QString HandsCollideWithSelf = "Collide With Self";
const QString HeadMouse = "Head Mouse";
const QString IncreaseAvatarSize = "Increase Avatar Size";
const QString IncreaseVoxelSize = "Increase Voxel Size";
@ -401,7 +410,6 @@ namespace MenuOption {
const QString Login = "Login";
const QString Log = "Log";
const QString Logout = "Logout";
const QString LookAtVectors = "Look-at Vectors";
const QString LowVelocityFilter = "Low Velocity Filter";
const QString MetavoxelEditor = "Metavoxel Editor...";
const QString Metavoxels = "Metavoxels";
@ -421,13 +429,15 @@ namespace MenuOption {
const QString Pair = "Pair";
const QString Particles = "Particles";
const QString PasteToVoxel = "Paste to Voxel...";
const QString PipelineWarnings = "Show Render Pipeline Warnings";
const QString PipelineWarnings = "Log Render Pipeline Warnings";
const QString Preferences = "Preferences...";
const QString Quit = "Quit";
const QString ReloadAllScripts = "Reload All Scripts";
const QString RenderBoundingCollisionShapes = "Bounding Collision Shapes";
const QString RenderHeadCollisionShapes = "Head Collision Shapes";
const QString RenderSkeletonCollisionShapes = "Skeleton Collision Shapes";
const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes";
const QString RenderFocusIndicator = "Show Eye Focus";
const QString RenderHeadCollisionShapes = "Show Head Collision Shapes";
const QString RenderLookAtVectors = "Show Look-at Vectors";
const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes";
const QString ResetAvatarSize = "Reset Avatar Size";
const QString RunningScripts = "Running Scripts";
const QString RunTimingTests = "Run Timing Tests";
@ -459,7 +469,7 @@ namespace MenuOption {
const QString VoxelMode = "Cycle Voxel Mode";
const QString Voxels = "Voxels";
const QString VoxelTextures = "Voxel Textures";
const QString WalletPrivateKey = "Wallet Private Key";
const QString WalletPrivateKey = "Wallet Private Key...";
}
void sendFakeEnterEvent();

View file

@ -11,7 +11,9 @@
#include <GLMHelpers.h>
#include <QFile>
#include <QMetaObject>
#include <QObject>
#include "Recorder.h"
@ -241,9 +243,14 @@ void Player::stopPlaying() {
// Cleanup audio thread
_injector->stop();
QObject::connect(_injector.data(), &AudioInjector::finished,
_injector.data(), &AudioInjector::deleteLater);
QObject::connect(_injector.data(), &AudioInjector::destroyed,
_audioThread, &QThread::quit);
QObject::connect(_audioThread, &QThread::finished,
_audioThread, &QThread::deleteLater);
_injector.clear();
_audioThread->exit();
_audioThread->deleteLater();
_audioThread = NULL;
qDebug() << "Recorder::stopPlaying()";
}
@ -309,13 +316,255 @@ bool Player::computeCurrentFrame() {
return true;
}
void writeRecordingToFile(RecordingPointer recording, QString file) {
// TODO
qDebug() << "Writing recording to " << file;
void writeRecordingToFile(RecordingPointer recording, QString filename) {
if (!recording || recording->getFrameNumber() < 1) {
qDebug() << "Can't save empty recording";
return;
}
qDebug() << "Writing recording to " << filename << ".";
QElapsedTimer timer;
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)){
return;
}
timer.start();
QDataStream fileStream(&file);
fileStream << recording->_timestamps;
RecordingFrame& baseFrame = recording->_frames[0];
int totalLength = 0;
// Blendshape coefficients
fileStream << baseFrame._blendshapeCoefficients;
totalLength += baseFrame._blendshapeCoefficients.size();
// Joint Rotations
int jointRotationSize = baseFrame._jointRotations.size();
fileStream << jointRotationSize;
for (int i = 0; i < jointRotationSize; ++i) {
fileStream << baseFrame._jointRotations[i].x << baseFrame._jointRotations[i].y << baseFrame._jointRotations[i].z << baseFrame._jointRotations[i].w;
}
totalLength += jointRotationSize;
// Translation
fileStream << baseFrame._translation.x << baseFrame._translation.y << baseFrame._translation.z;
totalLength += 1;
// Rotation
fileStream << baseFrame._rotation.x << baseFrame._rotation.y << baseFrame._rotation.z << baseFrame._rotation.w;
totalLength += 1;
// Scale
fileStream << baseFrame._scale;
totalLength += 1;
// Head Rotation
fileStream << baseFrame._headRotation.x << baseFrame._headRotation.y << baseFrame._headRotation.z << baseFrame._headRotation.w;
totalLength += 1;
// Lean Sideways
fileStream << baseFrame._leanSideways;
totalLength += 1;
// Lean Forward
fileStream << baseFrame._leanForward;
totalLength += 1;
for (int i = 1; i < recording->_timestamps.size(); ++i) {
QBitArray mask(totalLength);
int maskIndex = 0;
QByteArray buffer;
QDataStream stream(&buffer, QIODevice::WriteOnly);
RecordingFrame& previousFrame = recording->_frames[i - 1];
RecordingFrame& frame = recording->_frames[i];
// Blendshape coefficients
for (int i = 0; i < frame._blendshapeCoefficients.size(); ++i) {
if (frame._blendshapeCoefficients[i] != previousFrame._blendshapeCoefficients[i]) {
stream << frame._blendshapeCoefficients[i];
mask.setBit(maskIndex);
}
maskIndex++;
}
// Joint Rotations
for (int i = 0; i < frame._jointRotations.size(); ++i) {
if (frame._jointRotations[i] != previousFrame._jointRotations[i]) {
stream << frame._jointRotations[i].x << frame._jointRotations[i].y << frame._jointRotations[i].z << frame._jointRotations[i].w;
mask.setBit(maskIndex);
}
maskIndex++;
}
// Translation
if (frame._translation != previousFrame._translation) {
stream << frame._translation.x << frame._translation.y << frame._translation.z;
mask.setBit(maskIndex);
}
maskIndex++;
// Rotation
if (frame._rotation != previousFrame._rotation) {
stream << frame._rotation.x << frame._rotation.y << frame._rotation.z << frame._rotation.w;
mask.setBit(maskIndex);
}
maskIndex++;
// Scale
if (frame._scale != previousFrame._scale) {
stream << frame._scale;
mask.setBit(maskIndex);
}
maskIndex++;
// Head Rotation
if (frame._headRotation != previousFrame._headRotation) {
stream << frame._headRotation.x << frame._headRotation.y << frame._headRotation.z << frame._headRotation.w;
mask.setBit(maskIndex);
}
maskIndex++;
// Lean Sideways
if (frame._leanSideways != previousFrame._leanSideways) {
stream << frame._leanSideways;
mask.setBit(maskIndex);
}
maskIndex++;
// Lean Forward
if (frame._leanForward != previousFrame._leanForward) {
stream << frame._leanForward;
mask.setBit(maskIndex);
}
maskIndex++;
fileStream << mask;
fileStream << buffer;
}
fileStream << recording->_audio->getByteArray();
qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed() << " ms.";
}
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file) {
// TODO
qDebug() << "Reading recording from " << file;
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filename) {
qDebug() << "Reading recording from " << filename << ".";
if (!recording) {
recording.reset(new Recording());
}
QElapsedTimer timer;
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)){
return recording;
}
timer.start();
QDataStream fileStream(&file);
fileStream >> recording->_timestamps;
RecordingFrame baseFrame;
// Blendshape coefficients
fileStream >> baseFrame._blendshapeCoefficients;
// Joint Rotations
int jointRotationSize;
fileStream >> jointRotationSize;
baseFrame._jointRotations.resize(jointRotationSize);
for (int i = 0; i < jointRotationSize; ++i) {
fileStream >> baseFrame._jointRotations[i].x >> baseFrame._jointRotations[i].y >> baseFrame._jointRotations[i].z >> baseFrame._jointRotations[i].w;
}
fileStream >> baseFrame._translation.x >> baseFrame._translation.y >> baseFrame._translation.z;
fileStream >> baseFrame._rotation.x >> baseFrame._rotation.y >> baseFrame._rotation.z >> baseFrame._rotation.w;
fileStream >> baseFrame._scale;
fileStream >> baseFrame._headRotation.x >> baseFrame._headRotation.y >> baseFrame._headRotation.z >> baseFrame._headRotation.w;
fileStream >> baseFrame._leanSideways;
fileStream >> baseFrame._leanForward;
recording->_frames << baseFrame;
for (int i = 1; i < recording->_timestamps.size(); ++i) {
QBitArray mask;
QByteArray buffer;
QDataStream stream(&buffer, QIODevice::ReadOnly);
RecordingFrame frame;
RecordingFrame& previousFrame = recording->_frames.last();
fileStream >> mask;
fileStream >> buffer;
int maskIndex = 0;
// Blendshape Coefficients
frame._blendshapeCoefficients.resize(baseFrame._blendshapeCoefficients.size());
for (int i = 0; i < baseFrame._blendshapeCoefficients.size(); ++i) {
if (mask[maskIndex++]) {
stream >> frame._blendshapeCoefficients[i];
} else {
frame._blendshapeCoefficients[i] = previousFrame._blendshapeCoefficients[i];
}
}
// Joint Rotations
frame._jointRotations.resize(baseFrame._jointRotations.size());
for (int i = 0; i < baseFrame._jointRotations.size(); ++i) {
if (mask[maskIndex++]) {
stream >> frame._jointRotations[i].x >> frame._jointRotations[i].y >> frame._jointRotations[i].z >> frame._jointRotations[i].w;
} else {
frame._jointRotations[i] = previousFrame._jointRotations[i];
}
}
if (mask[maskIndex++]) {
stream >> frame._translation.x >> frame._translation.y >> frame._translation.z;
} else {
frame._translation = previousFrame._translation;
}
if (mask[maskIndex++]) {
stream >> frame._rotation.x >> frame._rotation.y >> frame._rotation.z >> frame._rotation.w;
} else {
frame._rotation = previousFrame._rotation;
}
if (mask[maskIndex++]) {
stream >> frame._scale;
} else {
frame._scale = previousFrame._scale;
}
if (mask[maskIndex++]) {
stream >> frame._headRotation.x >> frame._headRotation.y >> frame._headRotation.z >> frame._headRotation.w;
} else {
frame._headRotation = previousFrame._headRotation;
}
if (mask[maskIndex++]) {
stream >> frame._leanSideways;
} else {
frame._leanSideways = previousFrame._leanSideways;
}
if (mask[maskIndex++]) {
stream >> frame._leanForward;
} else {
frame._leanForward = previousFrame._leanForward;
}
recording->_frames << frame;
}
QByteArray audioArray;
fileStream >> audioArray;
recording->addAudioPacket(audioArray);
qDebug() << "Read " << file.size() << " bytes in " << timer.elapsed() << " ms.";
return recording;
}
}

View file

@ -137,6 +137,8 @@ public:
bool isPlaying() const;
qint64 elapsed() const;
RecordingPointer getRecording() const { return _recording; }
// Those should only be called if isPlaying() returns true
glm::quat getHeadRotation();
float getLeanSideways();

View file

@ -0,0 +1,47 @@
//
// SpeechRecognizer.h
// interface/src
//
// Created by Ryan Huffman on 07/31/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
//
#ifndef hifi_SpeechRecognizer_h
#define hifi_SpeechRecognizer_h
#include <QObject>
#include <QSet>
#include <QString>
class SpeechRecognizer : public QObject {
Q_OBJECT
public:
SpeechRecognizer();
~SpeechRecognizer();
void handleCommandRecognized(const char* command);
bool getEnabled() const { return _enabled; }
public slots:
void setEnabled(bool enabled);
void addCommand(const QString& command);
void removeCommand(const QString& command);
signals:
void commandRecognized(const QString& command);
void enabledUpdated(bool enabled);
protected:
void reloadCommands();
private:
bool _enabled;
QSet<QString> _commands;
void* _speechRecognizerDelegate;
void* _speechRecognizer;
};
#endif // hifi_SpeechRecognizer_h

View file

@ -0,0 +1,109 @@
//
// SpeechRecognizer.mm
// interface/src
//
// Created by Ryan Huffman on 07/31/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
//
#include <QtGlobal>
#ifdef Q_OS_MAC
#import <Foundation/Foundation.h>
#import <AppKit/NSSpeechRecognizer.h>
#import <AppKit/NSWorkspace.h>
#include <QDebug>
#include "SpeechRecognizer.h"
@interface SpeechRecognizerDelegate : NSObject <NSSpeechRecognizerDelegate> {
SpeechRecognizer* _listener;
}
- (void)setListener:(SpeechRecognizer*)listener;
- (void)speechRecognizer:(NSSpeechRecognizer*)sender didRecognizeCommand:(id)command;
@end
@implementation SpeechRecognizerDelegate
- (void)setListener:(SpeechRecognizer*)listener {
_listener = listener;
}
- (void)speechRecognizer:(NSSpeechRecognizer*)sender didRecognizeCommand:(id)command {
_listener->handleCommandRecognized(((NSString*)command).UTF8String);
}
@end
SpeechRecognizer::SpeechRecognizer() :
QObject(),
_enabled(false),
_commands(),
_speechRecognizerDelegate([[SpeechRecognizerDelegate alloc] init]),
_speechRecognizer(NULL) {
[(id)_speechRecognizerDelegate setListener:this];
}
SpeechRecognizer::~SpeechRecognizer() {
if (_speechRecognizer) {
[(id)_speechRecognizer dealloc];
}
if (_speechRecognizerDelegate) {
[(id)_speechRecognizerDelegate dealloc];
}
}
void SpeechRecognizer::handleCommandRecognized(const char* command) {
emit commandRecognized(QString(command));
}
void SpeechRecognizer::setEnabled(bool enabled) {
if (enabled == _enabled) {
return;
}
_enabled = enabled;
if (_enabled) {
_speechRecognizer = [[NSSpeechRecognizer alloc] init];
reloadCommands();
[(id)_speechRecognizer setDelegate:(id)_speechRecognizerDelegate];
[(id)_speechRecognizer startListening];
} else {
[(id)_speechRecognizer stopListening];
[(id)_speechRecognizer dealloc];
_speechRecognizer = NULL;
}
emit enabledUpdated(_enabled);
}
void SpeechRecognizer::reloadCommands() {
if (_speechRecognizer) {
NSMutableArray* cmds = [NSMutableArray array];
for (QSet<QString>::const_iterator iter = _commands.constBegin(); iter != _commands.constEnd(); iter++) {
[cmds addObject:[NSString stringWithUTF8String:(*iter).toLocal8Bit().data()]];
}
[(id)_speechRecognizer setCommands:cmds];
}
}
void SpeechRecognizer::addCommand(const QString& command) {
_commands.insert(command);
reloadCommands();
}
void SpeechRecognizer::removeCommand(const QString& command) {
_commands.remove(command);
reloadCommands();
}
#endif // Q_OS_MAC

View file

@ -302,7 +302,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
return;
}
glm::vec3 toTarget = cameraPosition - Application::getInstance()->getAvatar()->getPosition();
glm::vec3 toTarget = cameraPosition - getPosition();
float distanceToTarget = glm::length(toTarget);
{
@ -349,7 +349,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
}
// If this is the avatar being looked at, render a little ball above their head
if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::FocusIndicators)) {
if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) {
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
const float LOOK_AT_INDICATOR_OFFSET = 0.22f;
const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.75f };
@ -368,10 +368,12 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
// quick check before falling into the code below:
// (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m)
const float MIN_VOICE_SPHERE_DISTANCE = 12.0f;
if (distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) {
if (Menu::getInstance()->isOptionChecked(MenuOption::BlueSpeechSphere)
&& distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) {
// render voice intensity sphere for avatars that are farther away
const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE;
const float MIN_SPHERE_ANGLE = 1.0f * RADIANS_PER_DEGREE;
const float MIN_SPHERE_ANGLE = 0.5f * RADIANS_PER_DEGREE;
const float MIN_SPHERE_SIZE = 0.01f;
const float SPHERE_LOUDNESS_SCALING = 0.0005f;
const float SPHERE_COLOR[] = { 0.5f, 0.8f, 0.8f };
@ -392,7 +394,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
}
}
const float DISPLAYNAME_DISTANCE = 10.0f;
const float DISPLAYNAME_DISTANCE = 20.0f;
setShowDisplayName(renderMode == NORMAL_RENDER_MODE && distanceToTarget < DISPLAYNAME_DISTANCE);
if (renderMode != NORMAL_RENDER_MODE || (isMyAvatar() &&
Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON)) {

View file

@ -91,7 +91,7 @@ public:
const QVector<Model*>& getAttachmentModels() const { return _attachmentModels; }
glm::vec3 getChestPosition() const;
float getScale() const { return _scale; }
const glm::vec3& getVelocity() const { return _velocity; }
Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; }
const Head* getHead() const { return static_cast<const Head*>(_headData); }
Head* getHead() { return static_cast<Head*>(_headData); }
Hand* getHand() { return static_cast<Hand*>(_handData); }
@ -152,9 +152,9 @@ public:
Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const;
Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const;
glm::vec3 getAcceleration() const { return _acceleration; }
glm::vec3 getAngularVelocity() const { return _angularVelocity; }
glm::vec3 getAngularAcceleration() const { return _angularAcceleration; }
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; }
/// Scales a world space position vector relative to the avatar position and scale

View file

@ -82,7 +82,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
void AvatarManager::renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::renderAvatars()");
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderLookAtVectors);
glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition();

View file

@ -54,7 +54,7 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX
state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2]))
* glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1]))
* glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0]))
* joint.rotation);
* joint.rotation, DEFAULT_PRIORITY);
}
void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
@ -69,7 +69,7 @@ void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJ
glm::quat between = rotationBetween(front, lookAt);
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
joint.rotation);
joint.rotation, DEFAULT_PRIORITY);
}
void FaceModel::updateJointState(int index) {

View file

@ -508,40 +508,162 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) {
}
}
bool MyAvatar::isRecording() const {
bool MyAvatar::isRecording() {
if (!_recorder) {
return false;
}
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(this, "isRecording", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, result));
return result;
}
return _recorder && _recorder->isRecording();
}
RecorderPointer MyAvatar::startRecording() {
qint64 MyAvatar::recorderElapsed() {
if (!_recorder) {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
QMetaObject::invokeMethod(this, "recorderElapsed", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
return result;
}
return _recorder->elapsed();
}
void MyAvatar::startRecording() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "startRecording", Qt::BlockingQueuedConnection);
return;
}
if (!_recorder) {
_recorder = RecorderPointer(new Recorder(this));
}
Application::getInstance()->getAudio()->setRecorder(_recorder);
_recorder->startRecording();
return _recorder;
}
void MyAvatar::stopRecording() {
if (!_recorder) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stopRecording", Qt::BlockingQueuedConnection);
return;
}
if (_recorder) {
_recorder->stopRecording();
}
}
bool MyAvatar::isPlaying() const {
void MyAvatar::saveRecording(QString filename) {
if (!_recorder) {
qDebug() << "There is no recording to save";
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "saveRecording", Qt::BlockingQueuedConnection,
Q_ARG(QString, filename));
return;
}
if (_recorder) {
_recorder->saveToFile(filename);
}
}
bool MyAvatar::isPlaying() {
if (!_player) {
return false;
}
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, result));
return result;
}
return _player && _player->isPlaying();
}
PlayerPointer MyAvatar::startPlaying() {
qint64 MyAvatar::playerElapsed() {
if (!_player) {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
return result;
}
return _player->elapsed();
}
qint64 MyAvatar::playerLength() {
if (!_player) {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
return result;
}
return _player->getRecording()->getLength();
}
void MyAvatar::loadRecording(QString filename) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection,
Q_ARG(QString, filename));
return;
}
if (!_player) {
_player = PlayerPointer(new Player(this));
}
if (_recorder) {
_player->loadRecording(_recorder->getRecording());
_player->startPlaying();
_player->loadFromFile(filename);
}
void MyAvatar::loadLastRecording() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadLastRecording", Qt::BlockingQueuedConnection);
return;
}
return _player;
if (!_recorder) {
qDebug() << "There is no recording to load";
return;
}
if (!_player) {
_player = PlayerPointer(new Player(this));
}
_player->loadRecording(_recorder->getRecording());
}
void MyAvatar::startPlaying() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection);
return;
}
if (!_player) {
_player = PlayerPointer(new Player(this));
}
Application::getInstance()->getAudio()->setPlayer(_player);
_player->startPlaying();
}
void MyAvatar::stopPlaying() {
if (!_player) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stopPlaying", Qt::BlockingQueuedConnection);
return;
}
if (_player) {
_player->stopPlaying();
}
@ -927,36 +1049,39 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f);
}
const float JOINT_PRIORITY = 2.0f;
const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f;
const float RECORDER_PRIORITY = SCRIPT_PRIORITY + 1.0f;
void MyAvatar::setJointRotations(QVector<glm::quat> jointRotations) {
for (int i = 0; i < jointRotations.size(); ++i) {
if (i < _jointData.size()) {
_skeletonModel.setJointState(i, true, jointRotations[i], JOINT_PRIORITY + 1.0f);
}
int numStates = glm::min(_skeletonModel.getJointStateCount(), jointRotations.size());
for (int i = 0; i < numStates; ++i) {
// HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here
_skeletonModel.setJointState(i, true, jointRotations[i], RECORDER_PRIORITY);
}
}
void MyAvatar::setJointData(int index, const glm::quat& rotation) {
Avatar::setJointData(index, rotation);
if (QThread::currentThread() == thread()) {
_skeletonModel.setJointState(index, true, rotation, JOINT_PRIORITY);
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel.setJointState(index, true, rotation, SCRIPT_PRIORITY);
}
}
void MyAvatar::clearJointData(int index) {
Avatar::clearJointData(index);
if (QThread::currentThread() == thread()) {
_skeletonModel.setJointState(index, false, glm::quat(), JOINT_PRIORITY);
// HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority
_skeletonModel.setJointState(index, false, glm::quat(), 0.0f);
}
}
void MyAvatar::clearJointsData() {
for (int i = 0; i < _jointData.size(); ++i) {
Avatar::clearJointData(i);
if (QThread::currentThread() == thread()) {
_skeletonModel.clearJointState(i);
}
clearJointAnimationPriorities();
}
void MyAvatar::clearJointAnimationPriorities() {
int numStates = _skeletonModel.getJointStateCount();
for (int i = 0; i < numStates; ++i) {
_skeletonModel.clearJointAnimationPriority(i);
}
}

View file

@ -106,7 +106,15 @@ public:
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
static void sendKillAvatar();
Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); }
Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); }
Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); }
Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); }
Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); }
Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); }
Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; }
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
void updateLookAtTargetAvatar();
@ -120,6 +128,8 @@ public:
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
void clearJointAnimationPriorities();
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
@ -134,6 +144,10 @@ public:
/// Renders a laser pointer for UI picking
void renderLaserPointers();
glm::vec3 getLaserPointerTipPosition(const PalmData* palm);
const RecorderPointer getRecorder() const { return _recorder; }
const PlayerPointer getPlayer() const { return _player; }
public slots:
void goHome();
void increaseSize();
@ -157,14 +171,18 @@ public slots:
bool setModelReferential(int id);
bool setJointReferential(int id, int jointIndex);
const RecorderPointer getRecorder() const { return _recorder; }
bool isRecording() const;
RecorderPointer startRecording();
bool isRecording();
qint64 recorderElapsed();
void startRecording();
void stopRecording();
void saveRecording(QString filename);
const PlayerPointer getPlayer() const { return _player; }
bool isPlaying() const;
PlayerPointer startPlaying();
bool isPlaying();
qint64 playerElapsed();
qint64 playerLength();
void loadRecording(QString filename);
void loadLastRecording();
void startPlaying();
void stopPlaying();

View file

@ -51,7 +51,8 @@ void SkeletonModel::setJointStates(QVector<JointState> states) {
}
}
const float PALM_PRIORITY = 3.0f;
const float PALM_PRIORITY = DEFAULT_PRIORITY;
const float LEAN_PRIORITY = DEFAULT_PRIORITY;
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setTranslation(_owningAvatar->getPosition());
@ -230,7 +231,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
JointState& parentState = _jointStates[parentJointIndex];
parentState.setRotationInBindFrame(palmRotation, PALM_PRIORITY);
// lock hand to forearm by slamming its rotation (in parent-frame) to identity
_jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat());
_jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat(), PALM_PRIORITY);
} else {
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
}
@ -243,7 +244,7 @@ void SkeletonModel::updateJointState(int index) {
const JointState& parentState = _jointStates.at(joint.parentIndex);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (index == geometry.leanJointIndex) {
maybeUpdateLeanRotation(parentState, joint, state);
maybeUpdateLeanRotation(parentState, state);
} else if (index == geometry.neckJointIndex) {
maybeUpdateNeckRotation(parentState, joint, state);
@ -260,17 +261,18 @@ void SkeletonModel::updateJointState(int index) {
}
}
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, JointState& state) {
if (!_owningAvatar->isMyAvatar() || Application::getInstance()->getPrioVR()->isActive()) {
return;
}
// get the rotation axes in joint space and use them to adjust the rotation
glm::mat3 axes = glm::mat3_cast(glm::quat());
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)));
state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(),
glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(),
glm::normalize(inverse * axes[0])) * joint.rotation);
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat inverse = glm::inverse(parentState.getRotation() * state.getDefaultRotationInParentFrame());
state.setRotationInConstrainedFrame(
glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), inverse * zAxis)
* glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis)
* state.getFBXJoint().rotation, LEAN_PRIORITY);
}
void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
@ -576,6 +578,7 @@ SkeletonRagdoll* SkeletonModel::buildRagdoll() {
if (!_ragdoll) {
_ragdoll = new SkeletonRagdoll(this);
if (_enableShapes) {
clearShapes();
buildShapes();
}
}
@ -600,6 +603,7 @@ void SkeletonModel::buildShapes() {
if (!_ragdoll) {
_ragdoll = new SkeletonRagdoll(this);
}
_ragdoll->setRootIndex(geometry.rootJointIndex);
_ragdoll->initPoints();
QVector<VerletPoint>& points = _ragdoll->getPoints();
@ -614,15 +618,14 @@ void SkeletonModel::buildShapes() {
float radius = uniformScale * joint.boneRadius;
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
Shape::Type type = joint.shapeType;
if (i == 0 || (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON)) {
int parentIndex = joint.parentIndex;
if (parentIndex == -1 || radius < EPSILON) {
type = Shape::UNKNOWN_SHAPE;
} else if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) {
// this shape is forced to be a sphere
type = Shape::SPHERE_SHAPE;
}
if (radius < EPSILON) {
type = Shape::UNKNOWN_SHAPE;
}
Shape* shape = NULL;
int parentIndex = joint.parentIndex;
if (type == Shape::SPHERE_SHAPE) {
shape = new VerletSphereShape(radius, &(points[i]));
shape->setEntity(this);
@ -637,18 +640,16 @@ void SkeletonModel::buildShapes() {
points[i].setMass(mass);
totalMass += mass;
}
if (parentIndex != -1) {
if (shape && parentIndex != -1) {
// always disable collisions between joint and its parent
if (shape) {
disableCollisions(i, parentIndex);
}
disableCollisions(i, parentIndex);
}
_shapes.push_back(shape);
}
// set the mass of the root
if (numStates > 0) {
points[0].setMass(totalMass);
points[_ragdoll->getRootIndex()].setMass(totalMass);
}
// This method moves the shapes to their default positions in Model frame.

View file

@ -127,7 +127,7 @@ protected:
/// Updates the state of the joint at the specified index.
virtual void updateJointState(int index);
void maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
void maybeUpdateLeanRotation(const JointState& parentState, JointState& state);
void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);

View file

@ -36,8 +36,9 @@ void SkeletonRagdoll::stepForward(float deltaTime) {
void SkeletonRagdoll::slamPointPositions() {
QVector<JointState>& jointStates = _model->getJointStates();
int numStates = jointStates.size();
for (int i = 0; i < numStates; ++i) {
const int numPoints = _points.size();
assert(numPoints == jointStates.size());
for (int i = _rootIndex; i < numPoints; ++i) {
_points[i].initPosition(jointStates.at(i).getPosition());
}
}
@ -49,8 +50,7 @@ void SkeletonRagdoll::initPoints() {
initTransform();
// one point for each joint
QVector<JointState>& jointStates = _model->getJointStates();
int numStates = jointStates.size();
int numStates = _model->getJointStates().size();
_points.fill(VerletPoint(), numStates);
slamPointPositions();
}
@ -67,7 +67,7 @@ void SkeletonRagdoll::buildConstraints() {
float minBone = FLT_MAX;
float maxBone = -FLT_MAX;
QMultiMap<int, int> families;
for (int i = 0; i < numPoints; ++i) {
for (int i = _rootIndex; i < numPoints; ++i) {
const JointState& state = jointStates.at(i);
int parentIndex = state.getParentIndex();
if (parentIndex != -1) {
@ -105,7 +105,7 @@ void SkeletonRagdoll::buildConstraints() {
float MAX_STRENGTH = 0.6f;
float MIN_STRENGTH = 0.05f;
// each joint gets a MuscleConstraint to its parent
for (int i = 1; i < numPoints; ++i) {
for (int i = _rootIndex + 1; i < numPoints; ++i) {
const JointState& state = jointStates.at(i);
int p = state.getParentIndex();
if (p == -1) {

View file

@ -389,7 +389,6 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) {
if (theta > EPSILON) {
float rMag = glm::length(glm::vec3(r.x, r.y, r.z));
const float AVERAGE_CARA_FRAME_TIME = 0.04f;
const float ANGULAR_VELOCITY_MIN = 1.2f;
const float YAW_STANDARD_DEV_DEG = 2.5f;
_headAngularVelocity = theta / AVERAGE_CARA_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag;

View file

@ -194,12 +194,12 @@ float updateAndGetCoefficient(float * coefficient, float currentValue, bool scal
coefficient[AVG] = LONG_TERM_AVERAGE * coefficient[AVG] + (1.f - LONG_TERM_AVERAGE) * currentValue;
if (coefficient[MAX] > coefficient[MIN]) {
if (scaleToRange) {
return glm::clamp((currentValue - coefficient[AVG]) / (coefficient[MAX] - coefficient[MIN]), 0.f, 1.f);
return glm::clamp((currentValue - coefficient[AVG]) / (coefficient[MAX] - coefficient[MIN]), 0.0f, 1.0f);
} else {
return glm::clamp(currentValue - coefficient[AVG], 0.f, 1.f);
return glm::clamp(currentValue - coefficient[AVG], 0.0f, 1.0f);
}
} else {
return 0.f;
return 0.0f;
}
}
@ -242,13 +242,11 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
// Set blendshapes
float EYE_MAGNIFIER = 4.0f;
float rightEye = (updateAndGetCoefficient(_rightEye, packet.expressions[0])) * EYE_MAGNIFIER;
float rightEye = glm::clamp((updateAndGetCoefficient(_rightEye, packet.expressions[0])) * EYE_MAGNIFIER, 0.0f, 1.0f);
_blendshapeCoefficients[_rightBlinkIndex] = rightEye;
float leftEye = (updateAndGetCoefficient(_leftEye, packet.expressions[1])) * EYE_MAGNIFIER;
float leftEye = glm::clamp((updateAndGetCoefficient(_leftEye, packet.expressions[1])) * EYE_MAGNIFIER, 0.0f, 1.0f);
_blendshapeCoefficients[_leftBlinkIndex] = leftEye;
// Right eye = packet.expressions[0];
float leftBrow = 1.0f - rescaleCoef(packet.expressions[14]);
if (leftBrow < 0.5f) {
@ -270,9 +268,9 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
float JAW_OPEN_MAGNIFIER = 1.4f;
_blendshapeCoefficients[_jawOpenIndex] = rescaleCoef(packet.expressions[21]) * JAW_OPEN_MAGNIFIER;
_blendshapeCoefficients[_mouthSmileLeftIndex] = rescaleCoef(packet.expressions[24]);
_blendshapeCoefficients[_mouthSmileRightIndex] = rescaleCoef(packet.expressions[23]);
float SMILE_MULTIPLIER = 2.0f;
_blendshapeCoefficients[_mouthSmileLeftIndex] = glm::clamp(packet.expressions[24] * SMILE_MULTIPLIER, 0.0f, 1.0f);
_blendshapeCoefficients[_mouthSmileRightIndex] = glm::clamp(packet.expressions[23] * SMILE_MULTIPLIER, 0.0f, 1.0f);
} else {

View file

@ -26,11 +26,17 @@ using namespace fs;
using namespace std;
const quint16 FACESHIFT_PORT = 33433;
float STARTING_FACESHIFT_FRAME_TIME = 0.033f;
Faceshift::Faceshift() :
_tcpEnabled(true),
_tcpRetryCount(0),
_lastTrackingStateReceived(0),
_averageFrameTime(STARTING_FACESHIFT_FRAME_TIME),
_headAngularVelocity(0),
_headLinearVelocity(0),
_lastHeadTranslation(0),
_filteredHeadTranslation(0),
_eyeGazeLeftPitch(0.0f),
_eyeGazeLeftYaw(0.0f),
_eyeGazeRightPitch(0.0f),
@ -209,23 +215,41 @@ void Faceshift::receive(const QByteArray& buffer) {
float theta = 2 * acos(r.w);
if (theta > EPSILON) {
float rMag = glm::length(glm::vec3(r.x, r.y, r.z));
float AVERAGE_FACESHIFT_FRAME_TIME = 0.033f;
_headAngularVelocity = theta / AVERAGE_FACESHIFT_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag;
_headAngularVelocity = theta / _averageFrameTime * glm::vec3(r.x, r.y, r.z) / rMag;
} else {
_headAngularVelocity = glm::vec3(0,0,0);
}
_headRotation = newRotation;
const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f;
_headRotation = safeMix(_headRotation, newRotation, glm::clamp(glm::length(_headAngularVelocity) *
ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f));
const float TRANSLATION_SCALE = 0.02f;
_headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y,
-data.m_headTranslation.z) * TRANSLATION_SCALE;
glm::vec3 newHeadTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y,
-data.m_headTranslation.z) * TRANSLATION_SCALE;
_headLinearVelocity = (newHeadTranslation - _lastHeadTranslation) / _averageFrameTime;
const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f;
float velocityFilter = glm::clamp(1.0f - glm::length(_headLinearVelocity) *
LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
_filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * newHeadTranslation;
_lastHeadTranslation = newHeadTranslation;
_headTranslation = _filteredHeadTranslation;
_eyeGazeLeftPitch = -data.m_eyeGazeLeftPitch;
_eyeGazeLeftYaw = data.m_eyeGazeLeftYaw;
_eyeGazeRightPitch = -data.m_eyeGazeRightPitch;
_eyeGazeRightYaw = data.m_eyeGazeRightYaw;
_blendshapeCoefficients = QVector<float>::fromStdVector(data.m_coeffs);
_lastTrackingStateReceived = usecTimestampNow();
const float FRAME_AVERAGING_FACTOR = 0.99f;
quint64 usecsNow = usecTimestampNow();
if (_lastTrackingStateReceived != 0) {
_averageFrameTime = FRAME_AVERAGING_FACTOR * _averageFrameTime +
(1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastTrackingStateReceived) / 1000000.0f;
}
_lastTrackingStateReceived = usecsNow;
}
break;
}

View file

@ -98,8 +98,12 @@ private:
int _tcpRetryCount;
bool _tracking;
quint64 _lastTrackingStateReceived;
float _averageFrameTime;
glm::vec3 _headAngularVelocity;
glm::vec3 _headLinearVelocity;
glm::vec3 _lastHeadTranslation;
glm::vec3 _filteredHeadTranslation;
// degrees
float _eyeGazeLeftPitch;

View file

@ -145,7 +145,7 @@ glm::quat JointState::getVisibleRotationInParentFrame() const {
void JointState::restoreRotation(float fraction, float priority) {
assert(_fbxJoint != NULL);
if (priority == _animationPriority || _animationPriority == 0.0f) {
setRotationInConstrainedFrame(safeMix(_rotationInConstrainedFrame, _fbxJoint->rotation, fraction));
setRotationInConstrainedFrameInternal(safeMix(_rotationInConstrainedFrame, _fbxJoint->rotation, fraction));
_animationPriority = 0.0f;
}
}
@ -158,7 +158,7 @@ void JointState::setRotationInBindFrame(const glm::quat& rotation, float priorit
if (constrain && _constraint) {
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
}
setRotationInConstrainedFrame(targetRotation);
setRotationInConstrainedFrameInternal(targetRotation);
_animationPriority = priority;
}
}
@ -173,10 +173,6 @@ void JointState::clearTransformTranslation() {
_visibleTransform[3][2] = 0.0f;
}
void JointState::setRotation(const glm::quat& rotation, bool constrain, float priority) {
applyRotationDelta(rotation * glm::inverse(getRotation()), true, priority);
}
void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) {
// NOTE: delta is in model-frame
assert(_fbxJoint != NULL);
@ -193,7 +189,7 @@ void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, floa
_rotation = delta * getRotation();
return;
}
setRotationInConstrainedFrame(targetRotation);
setRotationInConstrainedFrameInternal(targetRotation);
}
/// Applies delta rotation to joint but mixes a little bit of the default pose as well.
@ -212,7 +208,7 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float
if (_constraint) {
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
}
setRotationInConstrainedFrame(targetRotation);
setRotationInConstrainedFrameInternal(targetRotation);
}
void JointState::mixVisibleRotationDelta(const glm::quat& delta, float mixFactor) {
@ -236,7 +232,17 @@ glm::quat JointState::computeVisibleParentRotation() const {
return _visibleRotation * glm::inverse(_fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation);
}
void JointState::setRotationInConstrainedFrame(const glm::quat& targetRotation) {
void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain) {
if (priority >= _animationPriority || _animationPriority == 0.0f) {
if (constrain && _constraint) {
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
}
setRotationInConstrainedFrameInternal(targetRotation);
_animationPriority = priority;
}
}
void JointState::setRotationInConstrainedFrameInternal(const glm::quat& targetRotation) {
glm::quat parentRotation = computeParentRotation();
_rotationInConstrainedFrame = targetRotation;
_transformChanged = true;
@ -258,6 +264,11 @@ const bool JointState::rotationIsDefault(const glm::quat& rotation, float tolera
glm::abs(rotation.w - defaultRotation.w) < tolerance;
}
glm::quat JointState::getDefaultRotationInParentFrame() const {
// NOTE: the result is constant and could be cached in the FBXJoint
return _fbxJoint->preRotation * _fbxJoint->rotation * _fbxJoint->postRotation;
}
const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const {
assert(_fbxJoint != NULL);
return _fbxJoint->translation;
@ -267,4 +278,4 @@ void JointState::slaveVisibleTransform() {
_visibleTransform = _transform;
_visibleRotation = getRotation();
_visibleRotationInConstrainedFrame = _rotationInConstrainedFrame;
}
}

View file

@ -19,6 +19,8 @@
#include <GLMHelpers.h>
#include <FBXReader.h>
const float DEFAULT_PRIORITY = 3.0f;
class AngularConstraint;
class JointState {
@ -60,9 +62,6 @@ public:
int getParentIndex() const { return _fbxJoint->parentIndex; }
/// \param rotation rotation of joint in model-frame
void setRotation(const glm::quat& rotation, bool constrain, float priority);
/// \param delta is in the model-frame
void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f);
@ -84,13 +83,14 @@ public:
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
void setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
void setRotationInConstrainedFrame(const glm::quat& targetRotation);
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false);
void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation);
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
const glm::quat& getVisibleRotationInConstrainedFrame() const { return _visibleRotationInConstrainedFrame; }
const bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const;
glm::quat getDefaultRotationInParentFrame() const;
const glm::vec3& getDefaultTranslationInConstrainedFrame() const;
@ -106,6 +106,7 @@ public:
glm::quat computeVisibleParentRotation() const;
private:
void setRotationInConstrainedFrameInternal(const glm::quat& targetRotation);
/// debug helper function
void loadBindRotation();

View file

@ -438,7 +438,7 @@ void Model::reset() {
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation);
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f);
}
}
@ -695,8 +695,13 @@ bool Model::getVisibleJointState(int index, glm::quat& rotation) const {
void Model::clearJointState(int index) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
state.setRotationInConstrainedFrame(glm::quat());
state._animationPriority = 0.0f;
state.setRotationInConstrainedFrame(glm::quat(), 0.0f);
}
}
void Model::clearJointAnimationPriority(int index) {
if (index != -1 && index < _jointStates.size()) {
_jointStates[index]._animationPriority = 0.0f;
}
}
@ -705,8 +710,7 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa
JointState& state = _jointStates[index];
if (priority >= state._animationPriority) {
if (valid) {
state.setRotationInConstrainedFrame(rotation);
state._animationPriority = priority;
state.setRotationInConstrainedFrame(rotation, priority);
} else {
state.restoreRotation(1.0f, priority);
}
@ -1739,10 +1743,7 @@ void AnimationHandle::applyFrame(float frameIndex) {
int mapping = _jointMappings.at(i);
if (mapping != -1) {
JointState& state = _model->_jointStates[mapping];
if (_priority >= state._animationPriority) {
state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction));
state._animationPriority = _priority;
}
state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction), _priority);
}
}
}

View file

@ -121,6 +121,9 @@ public:
/// Clear the joint states
void clearJointState(int index);
/// Clear the joint animation priority
void clearJointAnimationPriority(int index);
/// Sets the joint state at the specified index.
void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f);

View file

@ -295,7 +295,6 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
// filename if the directory is valid.
QString path = "";
QFileInfo fileInfo = QFileInfo(directory);
qDebug() << "File: " << directory << fileInfo.isFile();
if (fileInfo.isDir()) {
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
path = fileInfo.filePath();
@ -303,7 +302,6 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter);
fileDialog.setAcceptMode(acceptMode);
qDebug() << "Opening!";
QUrl fileUrl(directory);
if (acceptMode == QFileDialog::AcceptSave) {
fileDialog.setFileMode(QFileDialog::Directory);

View file

@ -282,19 +282,10 @@ void Stats::display(
pingVoxel = totalPingVoxel/voxelServerCount;
}
Audio* audio = Application::getInstance()->getAudio();
lines = _expanded ? 4 : 3;
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5;
char audioJitter[30];
sprintf(audioJitter,
"Buffer msecs %.1f",
audio->getDesiredJitterBufferFrames() * BUFFER_SEND_INTERVAL_USECS / (float)USECS_PER_MSEC);
drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color);
char audioPing[30];
sprintf(audioPing, "Audio ping: %d", pingAudio);
@ -698,27 +689,6 @@ void Stats::display(
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
}
// draw local light stats
QVector<Model::LocalLight> localLights = Application::getInstance()->getAvatarManager().getLocalLights();
verticalOffset = 400;
horizontalOffset = 20;
char buffer[128];
for (int i = 0; i < localLights.size(); i++) {
glm::vec3 lightDirection = localLights.at(i).direction;
snprintf(buffer, sizeof(buffer), "Light %d direction (%.2f, %.2f, %.2f)", i, lightDirection.x, lightDirection.y, lightDirection.z);
drawText(horizontalOffset, verticalOffset, scale, rotation, font, buffer, color);
verticalOffset += STATS_PELS_PER_LINE;
glm::vec3 lightColor = localLights.at(i).color;
snprintf(buffer, sizeof(buffer), "Light %d color (%.2f, %.2f, %.2f)", i, lightColor.x, lightColor.y, lightColor.z);
drawText(horizontalOffset, verticalOffset, scale, rotation, font, buffer, color);
verticalOffset += STATS_PELS_PER_LINE;
}
}

View file

@ -687,7 +687,7 @@ QVector<glm::quat> AvatarData::getJointRotations() const {
if (QThread::currentThread() != thread()) {
QVector<glm::quat> result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this),
"getJointRotation", Qt::BlockingQueuedConnection,
"getJointRotations", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVector<glm::quat>, result));
return result;
}
@ -702,7 +702,7 @@ void AvatarData::setJointRotations(QVector<glm::quat> jointRotations) {
if (QThread::currentThread() != thread()) {
QVector<glm::quat> result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this),
"setJointRotation", Qt::BlockingQueuedConnection,
"setJointRotations", Qt::BlockingQueuedConnection,
Q_ARG(QVector<glm::quat>, jointRotations));
}
for (int i = 0; i < jointRotations.size(); ++i) {

View file

@ -78,6 +78,8 @@ KeyEvent::KeyEvent(const QKeyEvent& event) {
text = "LEFT";
} else if (key == Qt::Key_Right) {
text = "RIGHT";
} else if (key == Qt::Key_Space) {
text = "SPACE";
} else if (key == Qt::Key_Escape) {
text = "ESC";
} else if (key == Qt::Key_Tab) {
@ -220,6 +222,8 @@ void keyEventFromScriptValue(const QScriptValue& object, KeyEvent& event) {
} else if (event.text.toUpper() == "RIGHT") {
event.key = Qt::Key_Right;
event.isKeypad = true;
} else if (event.text.toUpper() == "SPACE") {
event.key = Qt::Key_Space;
} else if (event.text.toUpper() == "ESC") {
event.key = Qt::Key_Escape;
} else if (event.text.toUpper() == "TAB") {

View file

@ -20,7 +20,7 @@
#include "SharedUtil.h" // for EPSILON
Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f),
_accumulatedMovement(0.0f), _simulation(NULL) {
_rootIndex(0), _accumulatedMovement(0.0f), _simulation(NULL) {
}
Ragdoll::~Ragdoll() {
@ -35,7 +35,7 @@ void Ragdoll::stepForward(float deltaTime) {
updateSimulationTransforms(_translation - _simulation->getTranslation(), _rotation);
}
int numPoints = _points.size();
for (int i = 0; i < numPoints; ++i) {
for (int i = _rootIndex; i < numPoints; ++i) {
_points[i].integrateForward();
}
}
@ -77,7 +77,9 @@ void Ragdoll::initTransform() {
}
void Ragdoll::setTransform(const glm::vec3& translation, const glm::quat& rotation) {
_translation = translation;
if (translation != _translation) {
_translation = translation;
}
_rotation = rotation;
}
@ -95,7 +97,7 @@ void Ragdoll::updateSimulationTransforms(const glm::vec3& translation, const glm
// apply the deltas to all ragdollPoints
int numPoints = _points.size();
for (int i = 0; i < numPoints; ++i) {
for (int i = _rootIndex; i < numPoints; ++i) {
_points[i].move(deltaPosition, deltaRotation, _translationInSimulationFrame);
}
@ -111,7 +113,7 @@ void Ragdoll::setMassScale(float scale) {
if (scale != _massScale) {
float rescale = scale / _massScale;
int numPoints = _points.size();
for (int i = 0; i < numPoints; ++i) {
for (int i = _rootIndex; i < numPoints; ++i) {
_points[i].setMass(rescale * _points[i].getMass());
}
_massScale = scale;
@ -122,10 +124,10 @@ void Ragdoll::removeRootOffset(bool accumulateMovement) {
const int numPoints = _points.size();
if (numPoints > 0) {
// shift all points so that the root aligns with the the ragdoll's position in the simulation
glm::vec3 offset = _translationInSimulationFrame - _points[0]._position;
glm::vec3 offset = _translationInSimulationFrame - _points[_rootIndex]._position;
float offsetLength = glm::length(offset);
if (offsetLength > EPSILON) {
for (int i = 0; i < numPoints; ++i) {
for (int i = _rootIndex; i < numPoints; ++i) {
_points[i].shift(offset);
}
const float MIN_ROOT_OFFSET = 0.02f;

View file

@ -52,6 +52,10 @@ public:
void setMassScale(float scale);
float getMassScale() const { return _massScale; }
// the ragdoll's rootIndex (within a Model's joints) is not always zero so must be settable
void setRootIndex(int index) { _rootIndex = index; }
int getRootIndex() const { return _rootIndex; }
void clearConstraintsAndPoints();
virtual void initPoints() = 0;
virtual void buildConstraints() = 0;
@ -66,6 +70,7 @@ protected:
glm::quat _rotation; // world-frame
glm::vec3 _translationInSimulationFrame;
glm::quat _rotationInSimulationFrame;
int _rootIndex;
QVector<VerletPoint> _points;
QVector<DistanceConstraint*> _boneConstraints;