mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-05 23:00:18 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into overlaysupport
This commit is contained in:
commit
8e284a55b1
30 changed files with 577 additions and 353 deletions
|
@ -7,9 +7,11 @@
|
||||||
//
|
//
|
||||||
// Captures mouse clicks and edits voxels accordingly.
|
// Captures mouse clicks and edits voxels accordingly.
|
||||||
//
|
//
|
||||||
// click = create a new voxel on this face, same color as old
|
// click = create a new voxel on this face, same color as old (default color picker state)
|
||||||
// Alt + click = delete this voxel
|
// right click or control + click = delete this voxel
|
||||||
// shift + click = recolor this voxel
|
// shift + click = recolor this voxel
|
||||||
|
// 1 - 8 = pick new color from palette
|
||||||
|
// 9 = create a new voxel in front of the camera
|
||||||
//
|
//
|
||||||
// Click and drag to create more new voxels in the same direction
|
// Click and drag to create more new voxels in the same direction
|
||||||
//
|
//
|
||||||
|
@ -18,15 +20,41 @@ function vLength(v) {
|
||||||
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vMinus(a, b) {
|
||||||
|
var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
var NEW_VOXEL_SIZE = 1.0;
|
||||||
|
var NEW_VOXEL_DISTANCE_FROM_CAMERA = 3.0;
|
||||||
|
var ORBIT_RATE_ALTITUDE = 200.0;
|
||||||
|
var ORBIT_RATE_AZIMUTH = 90.0;
|
||||||
|
var PIXELS_PER_EXTRUDE_VOXEL = 16;
|
||||||
|
|
||||||
|
var oldMode = Camera.getMode();
|
||||||
|
|
||||||
var key_alt = false;
|
var key_alt = false;
|
||||||
var key_shift = false;
|
var key_shift = false;
|
||||||
var isAdding = false;
|
var isAdding = false;
|
||||||
|
var isExtruding = false;
|
||||||
|
var isOrbiting = false;
|
||||||
|
var orbitAzimuth = 0.0;
|
||||||
|
var orbitAltitude = 0.0;
|
||||||
|
var orbitCenter = { x: 0, y: 0, z: 0 };
|
||||||
|
var orbitPosition = { x: 0, y: 0, z: 0 };
|
||||||
|
var orbitRadius = 0.0;
|
||||||
|
var extrudeDirection = { x: 0, y: 0, z: 0 };
|
||||||
|
var extrudeScale = 0.0;
|
||||||
var lastVoxelPosition = { x: 0, y: 0, z: 0 };
|
var lastVoxelPosition = { x: 0, y: 0, z: 0 };
|
||||||
var lastVoxelColor = { red: 0, green: 0, blue: 0 };
|
var lastVoxelColor = { red: 0, green: 0, blue: 0 };
|
||||||
var lastVoxelScale = 0;
|
var lastVoxelScale = 0;
|
||||||
var dragStart = { x: 0, y: 0 };
|
var dragStart = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
var mouseX = 0;
|
||||||
|
var mouseY = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create a table of the different colors you can choose
|
// Create a table of the different colors you can choose
|
||||||
var colors = new Array();
|
var colors = new Array();
|
||||||
colors[0] = { red: 237, green: 175, blue: 0 };
|
colors[0] = { red: 237, green: 175, blue: 0 };
|
||||||
|
@ -37,31 +65,70 @@ colors[4] = { red: 193, green: 99, blue: 122 };
|
||||||
colors[5] = { red: 255, green: 54, blue: 69 };
|
colors[5] = { red: 255, green: 54, blue: 69 };
|
||||||
colors[6] = { red: 124, green: 36, blue: 36 };
|
colors[6] = { red: 124, green: 36, blue: 36 };
|
||||||
colors[7] = { red: 63, green: 35, blue: 19 };
|
colors[7] = { red: 63, green: 35, blue: 19 };
|
||||||
var numColors = 6;
|
var numColors = 8;
|
||||||
var whichColor = 0;
|
var whichColor = -1; // Starting color is 'Copy' mode
|
||||||
|
|
||||||
// Create sounds for adding, deleting, recoloring voxels
|
// Create sounds for adding, deleting, recoloring voxels
|
||||||
var addSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Electronic/ElectronicBurst1.raw");
|
var addSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+create.raw");
|
||||||
var deleteSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Bubbles/bubbles1.raw");
|
var deleteSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+delete.raw");
|
||||||
var changeColorSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Electronic/ElectronicBurst6.raw");
|
var changeColorSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+edit.raw");
|
||||||
|
var clickSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Switches+and+sliders/toggle+switch+-+medium.raw");
|
||||||
var audioOptions = new AudioInjectionOptions();
|
var audioOptions = new AudioInjectionOptions();
|
||||||
|
audioOptions.volume = 0.5;
|
||||||
|
|
||||||
|
function setAudioPosition() {
|
||||||
|
var camera = Camera.getPosition();
|
||||||
|
var forwardVector = Quat.getFront(MyAvatar.orientation);
|
||||||
|
audioOptions.position = Vec3.sum(camera, forwardVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNewVoxelPosition() {
|
||||||
|
var camera = Camera.getPosition();
|
||||||
|
var forwardVector = Quat.getFront(MyAvatar.orientation);
|
||||||
|
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, NEW_VOXEL_DISTANCE_FROM_CAMERA));
|
||||||
|
return newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixEulerAngles(eulers) {
|
||||||
|
var rVal = { x: 0, y: 0, z: eulers.z };
|
||||||
|
if (eulers.x >= 90.0) {
|
||||||
|
rVal.x = 180.0 - eulers.x;
|
||||||
|
rVal.y = eulers.y - 180.0;
|
||||||
|
} else if (eulers.x <= -90.0) {
|
||||||
|
rVal.x = 180.0 - eulers.x;
|
||||||
|
rVal.y = eulers.y - 180.0;
|
||||||
|
}
|
||||||
|
return rVal;
|
||||||
|
}
|
||||||
|
|
||||||
function mousePressEvent(event) {
|
function mousePressEvent(event) {
|
||||||
|
mouseX = event.x;
|
||||||
|
mouseY = event.y;
|
||||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||||
var intersection = Voxels.findRayIntersection(pickRay);
|
var intersection = Voxels.findRayIntersection(pickRay);
|
||||||
audioOptions.volume = 1.0;
|
audioOptions.position = Vec3.sum(pickRay.origin, pickRay.direction);
|
||||||
audioOptions.position = { x: intersection.voxel.x, y: intersection.voxel.y, z: intersection.voxel.z };
|
|
||||||
|
|
||||||
if (intersection.intersects) {
|
if (intersection.intersects) {
|
||||||
if (key_alt) {
|
if (event.isAlt) {
|
||||||
|
// start orbit camera!
|
||||||
|
var cameraPosition = Camera.getPosition();
|
||||||
|
oldMode = Camera.getMode();
|
||||||
|
Camera.setMode("independent");
|
||||||
|
isOrbiting = true;
|
||||||
|
Camera.keepLookingAt(intersection.intersection);
|
||||||
|
// get position for initial azimuth, elevation
|
||||||
|
orbitCenter = intersection.intersection;
|
||||||
|
var orbitVector = Vec3.subtract(cameraPosition, orbitCenter);
|
||||||
|
orbitRadius = vLength(orbitVector);
|
||||||
|
orbitAzimuth = Math.atan2(orbitVector.z, orbitVector.x);
|
||||||
|
orbitAltitude = Math.asin(orbitVector.y / Vec3.length(orbitVector));
|
||||||
|
|
||||||
|
} else if (event.isRightButton || event.isControl) {
|
||||||
// Delete voxel
|
// Delete voxel
|
||||||
Voxels.eraseVoxel(intersection.voxel.x, intersection.voxel.y, intersection.voxel.z, intersection.voxel.s);
|
Voxels.eraseVoxel(intersection.voxel.x, intersection.voxel.y, intersection.voxel.z, intersection.voxel.s);
|
||||||
Audio.playSound(deleteSound, audioOptions);
|
Audio.playSound(deleteSound, audioOptions);
|
||||||
|
|
||||||
} else if (key_shift) {
|
} else if (event.isShifted) {
|
||||||
// Recolor Voxel
|
// Recolor Voxel
|
||||||
whichColor++;
|
|
||||||
if (whichColor == numColors) whichColor = 0;
|
|
||||||
Voxels.setVoxel(intersection.voxel.x,
|
Voxels.setVoxel(intersection.voxel.x,
|
||||||
intersection.voxel.y,
|
intersection.voxel.y,
|
||||||
intersection.voxel.z,
|
intersection.voxel.z,
|
||||||
|
@ -70,7 +137,9 @@ function mousePressEvent(event) {
|
||||||
Audio.playSound(changeColorSound, audioOptions);
|
Audio.playSound(changeColorSound, audioOptions);
|
||||||
} else {
|
} else {
|
||||||
// Add voxel on face
|
// Add voxel on face
|
||||||
var newVoxel = {
|
if (whichColor == -1) {
|
||||||
|
// Copy mode - use clicked voxel color
|
||||||
|
var newVoxel = {
|
||||||
x: intersection.voxel.x,
|
x: intersection.voxel.x,
|
||||||
y: intersection.voxel.y,
|
y: intersection.voxel.y,
|
||||||
z: intersection.voxel.z,
|
z: intersection.voxel.z,
|
||||||
|
@ -78,6 +147,16 @@ function mousePressEvent(event) {
|
||||||
red: intersection.voxel.red,
|
red: intersection.voxel.red,
|
||||||
green: intersection.voxel.green,
|
green: intersection.voxel.green,
|
||||||
blue: intersection.voxel.blue };
|
blue: intersection.voxel.blue };
|
||||||
|
} else {
|
||||||
|
var newVoxel = {
|
||||||
|
x: intersection.voxel.x,
|
||||||
|
y: intersection.voxel.y,
|
||||||
|
z: intersection.voxel.z,
|
||||||
|
s: intersection.voxel.s,
|
||||||
|
red: colors[whichColor].red,
|
||||||
|
green: colors[whichColor].green,
|
||||||
|
blue: colors[whichColor].blue };
|
||||||
|
}
|
||||||
|
|
||||||
if (intersection.face == "MIN_X_FACE") {
|
if (intersection.face == "MIN_X_FACE") {
|
||||||
newVoxel.x -= newVoxel.s;
|
newVoxel.x -= newVoxel.s;
|
||||||
|
@ -108,55 +187,113 @@ function mousePressEvent(event) {
|
||||||
function keyPressEvent(event) {
|
function keyPressEvent(event) {
|
||||||
key_alt = event.isAlt;
|
key_alt = event.isAlt;
|
||||||
key_shift = event.isShifted;
|
key_shift = event.isShifted;
|
||||||
|
var nVal = parseInt(event.text);
|
||||||
|
if (event.text == "0") {
|
||||||
|
print("Color = Copy");
|
||||||
|
whichColor = -1;
|
||||||
|
Audio.playSound(clickSound, audioOptions);
|
||||||
|
} else if ((nVal > 0) && (nVal <= numColors)) {
|
||||||
|
whichColor = nVal - 1;
|
||||||
|
print("Color = " + (whichColor + 1));
|
||||||
|
Audio.playSound(clickSound, audioOptions);
|
||||||
|
} else if (event.text == "9") {
|
||||||
|
// Create a brand new 1 meter voxel in front of your avatar
|
||||||
|
var color = whichColor;
|
||||||
|
if (color == -1) color = 0;
|
||||||
|
var newPosition = getNewVoxelPosition();
|
||||||
|
var newVoxel = {
|
||||||
|
x: newPosition.x,
|
||||||
|
y: newPosition.y ,
|
||||||
|
z: newPosition.z,
|
||||||
|
s: NEW_VOXEL_SIZE,
|
||||||
|
red: colors[color].red,
|
||||||
|
green: colors[color].green,
|
||||||
|
blue: colors[color].blue };
|
||||||
|
Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue);
|
||||||
|
setAudioPosition();
|
||||||
|
Audio.playSound(addSound, audioOptions);
|
||||||
|
} else if (event.text == " ") {
|
||||||
|
// Reset my orientation!
|
||||||
|
var orientation = { x:0, y:0, z:0, w:1 };
|
||||||
|
Camera.setOrientation(orientation);
|
||||||
|
MyAvatar.orientation = orientation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyReleaseEvent(event) {
|
function keyReleaseEvent(event) {
|
||||||
key_alt = false;
|
key_alt = false;
|
||||||
key_shift = false;
|
key_shift = false;
|
||||||
}
|
}
|
||||||
function mouseMoveEvent(event) {
|
function mouseMoveEvent(event) {
|
||||||
|
if (isOrbiting) {
|
||||||
|
var cameraOrientation = Camera.getOrientation();
|
||||||
|
var origEulers = Quat.safeEulerAngles(cameraOrientation);
|
||||||
|
var newEulers = fixEulerAngles(Quat.safeEulerAngles(cameraOrientation));
|
||||||
|
var dx = event.x - mouseX;
|
||||||
|
var dy = event.y - mouseY;
|
||||||
|
orbitAzimuth += dx / ORBIT_RATE_AZIMUTH;
|
||||||
|
orbitAltitude += dy / ORBIT_RATE_ALTITUDE;
|
||||||
|
var orbitVector = { x:(Math.cos(orbitAltitude) * Math.cos(orbitAzimuth)) * orbitRadius,
|
||||||
|
y:Math.sin(orbitAltitude) * orbitRadius,
|
||||||
|
z:(Math.cos(orbitAltitude) * Math.sin(orbitAzimuth)) * orbitRadius };
|
||||||
|
orbitPosition = Vec3.sum(orbitCenter, orbitVector);
|
||||||
|
Camera.setPosition(orbitPosition);
|
||||||
|
mouseX = event.x;
|
||||||
|
mouseY = event.y;
|
||||||
|
}
|
||||||
if (isAdding) {
|
if (isAdding) {
|
||||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
// Watch the drag direction to tell which way to 'extrude' this voxel
|
||||||
var lastVoxelDistance = { x: pickRay.origin.x - lastVoxelPosition.x,
|
if (!isExtruding) {
|
||||||
|
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||||
|
var lastVoxelDistance = { x: pickRay.origin.x - lastVoxelPosition.x,
|
||||||
y: pickRay.origin.y - lastVoxelPosition.y,
|
y: pickRay.origin.y - lastVoxelPosition.y,
|
||||||
z: pickRay.origin.z - lastVoxelPosition.z };
|
z: pickRay.origin.z - lastVoxelPosition.z };
|
||||||
var distance = vLength(lastVoxelDistance);
|
var distance = vLength(lastVoxelDistance);
|
||||||
var mouseSpot = { x: pickRay.direction.x * distance, y: pickRay.direction.y * distance, z: pickRay.direction.z * distance };
|
var mouseSpot = { x: pickRay.direction.x * distance, y: pickRay.direction.y * distance, z: pickRay.direction.z * distance };
|
||||||
mouseSpot.x += pickRay.origin.x;
|
mouseSpot.x += pickRay.origin.x;
|
||||||
mouseSpot.y += pickRay.origin.y;
|
mouseSpot.y += pickRay.origin.y;
|
||||||
mouseSpot.z += pickRay.origin.z;
|
mouseSpot.z += pickRay.origin.z;
|
||||||
var dx = mouseSpot.x - lastVoxelPosition.x;
|
var dx = mouseSpot.x - lastVoxelPosition.x;
|
||||||
var dy = mouseSpot.y - lastVoxelPosition.y;
|
var dy = mouseSpot.y - lastVoxelPosition.y;
|
||||||
var dz = mouseSpot.z - lastVoxelPosition.z;
|
var dz = mouseSpot.z - lastVoxelPosition.z;
|
||||||
if (dx > lastVoxelScale) {
|
extrudeScale = lastVoxelScale;
|
||||||
lastVoxelPosition.x += lastVoxelScale;
|
extrudeDirection = { x: 0, y: 0, z: 0 };
|
||||||
Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
|
isExtruding = true;
|
||||||
lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
|
if (dx > lastVoxelScale) extrudeDirection.x = extrudeScale;
|
||||||
} else if (dx < -lastVoxelScale) {
|
else if (dx < -lastVoxelScale) extrudeDirection.x = -extrudeScale;
|
||||||
lastVoxelPosition.x -= lastVoxelScale;
|
else if (dy > lastVoxelScale) extrudeDirection.y = extrudeScale;
|
||||||
Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
|
else if (dy < -lastVoxelScale) extrudeDirection.y = -extrudeScale;
|
||||||
lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
|
else if (dz > lastVoxelScale) extrudeDirection.z = extrudeScale;
|
||||||
} else if (dy > lastVoxelScale) {
|
else if (dz < -lastVoxelScale) extrudeDirection.z = -extrudeScale;
|
||||||
lastVoxelPosition.y += lastVoxelScale;
|
else isExtruding = false;
|
||||||
Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
|
} else {
|
||||||
lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
|
// We have got an extrusion direction, now look for mouse move beyond threshold to add new voxel
|
||||||
} else if (dy < -lastVoxelScale) {
|
var dx = event.x - mouseX;
|
||||||
lastVoxelPosition.y -= lastVoxelScale;
|
var dy = event.y - mouseY;
|
||||||
Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
|
if (Math.sqrt(dx*dx + dy*dy) > PIXELS_PER_EXTRUDE_VOXEL) {
|
||||||
lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
|
lastVoxelPosition = Vec3.sum(lastVoxelPosition, extrudeDirection);
|
||||||
} else if (dz > lastVoxelScale) {
|
Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
|
||||||
lastVoxelPosition.z += lastVoxelScale;
|
extrudeScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
|
||||||
Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
|
mouseX = event.x;
|
||||||
lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
|
mouseY = event.y;
|
||||||
} else if (dz < -lastVoxelScale) {
|
}
|
||||||
lastVoxelPosition.z -= lastVoxelScale;
|
|
||||||
Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
|
|
||||||
lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mouseReleaseEvent(event) {
|
function mouseReleaseEvent(event) {
|
||||||
|
if (isOrbiting) {
|
||||||
|
var cameraOrientation = Camera.getOrientation();
|
||||||
|
var eulers = Quat.safeEulerAngles(cameraOrientation);
|
||||||
|
MyAvatar.position = Camera.getPosition();
|
||||||
|
MyAvatar.orientation = cameraOrientation;
|
||||||
|
Camera.stopLooking();
|
||||||
|
Camera.setMode(oldMode);
|
||||||
|
Camera.setOrientation(cameraOrientation);
|
||||||
|
}
|
||||||
isAdding = false;
|
isAdding = false;
|
||||||
|
isOrbiting = false;
|
||||||
|
isExtruding = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.mousePressEvent.connect(mousePressEvent);
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
|
|
|
@ -2149,15 +2149,6 @@ void Application::updateThreads(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::updateParticles(float deltaTime) {
|
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
|
||||||
PerformanceWarning warn(showWarnings, "Application::updateParticles()");
|
|
||||||
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::ParticleCloud)) {
|
|
||||||
_cloud.simulate(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::updateMetavoxels(float deltaTime) {
|
void Application::updateMetavoxels(float deltaTime) {
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()");
|
PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()");
|
||||||
|
@ -2279,7 +2270,6 @@ void Application::update(float deltaTime) {
|
||||||
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
|
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
|
||||||
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
||||||
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
|
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
|
||||||
updateParticles(deltaTime); // Simulate particle cloud movements
|
|
||||||
updateMetavoxels(deltaTime); // update metavoxels
|
updateMetavoxels(deltaTime); // update metavoxels
|
||||||
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
|
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
|
||||||
updateDialogs(deltaTime); // update various stats dialogs if present
|
updateDialogs(deltaTime); // update various stats dialogs if present
|
||||||
|
@ -2714,10 +2704,6 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
||||||
// disable specular lighting for ground and voxels
|
// disable specular lighting for ground and voxels
|
||||||
glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR);
|
glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR);
|
||||||
|
|
||||||
// Draw Cloud Particles
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::ParticleCloud)) {
|
|
||||||
_cloud.render();
|
|
||||||
}
|
|
||||||
// Draw voxels
|
// Draw voxels
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||||
|
@ -4004,6 +3990,32 @@ void Application::saveScripts() {
|
||||||
settings->endArray();
|
settings->endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::stopAllScripts() {
|
||||||
|
// stops all current running scripts
|
||||||
|
QList<QAction*> scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions();
|
||||||
|
foreach (QAction* scriptAction, scriptActions) {
|
||||||
|
scriptAction->activate(QAction::Trigger);
|
||||||
|
qDebug() << "stopping script..." << scriptAction->text();
|
||||||
|
}
|
||||||
|
_activeScripts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::reloadAllScripts() {
|
||||||
|
// remember all the current scripts so we can reload them
|
||||||
|
QStringList reloadList = _activeScripts;
|
||||||
|
// reloads all current running scripts
|
||||||
|
QList<QAction*> scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions();
|
||||||
|
foreach (QAction* scriptAction, scriptActions) {
|
||||||
|
scriptAction->activate(QAction::Trigger);
|
||||||
|
qDebug() << "stopping script..." << scriptAction->text();
|
||||||
|
}
|
||||||
|
_activeScripts.clear();
|
||||||
|
foreach (QString scriptName, reloadList){
|
||||||
|
qDebug() << "reloading script..." << scriptName;
|
||||||
|
loadScript(scriptName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::removeScriptName(const QString& fileNameString) {
|
void Application::removeScriptName(const QString& fileNameString) {
|
||||||
_activeScripts.removeOne(fileNameString);
|
_activeScripts.removeOne(fileNameString);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
|
|
||||||
#include "BandwidthMeter.h"
|
#include "BandwidthMeter.h"
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
#include "Cloud.h"
|
|
||||||
#include "DatagramProcessor.h"
|
#include "DatagramProcessor.h"
|
||||||
#include "Environment.h"
|
#include "Environment.h"
|
||||||
#include "GLCanvas.h"
|
#include "GLCanvas.h"
|
||||||
|
@ -233,6 +232,8 @@ public slots:
|
||||||
void loadDialog();
|
void loadDialog();
|
||||||
void toggleLogDialog();
|
void toggleLogDialog();
|
||||||
void initAvatarAndViewFrustum();
|
void initAvatarAndViewFrustum();
|
||||||
|
void stopAllScripts();
|
||||||
|
void reloadAllScripts();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void timer();
|
void timer();
|
||||||
|
@ -285,7 +286,6 @@ private:
|
||||||
void updateSixense(float deltaTime);
|
void updateSixense(float deltaTime);
|
||||||
void updateSerialDevices(float deltaTime);
|
void updateSerialDevices(float deltaTime);
|
||||||
void updateThreads(float deltaTime);
|
void updateThreads(float deltaTime);
|
||||||
void updateParticles(float deltaTime);
|
|
||||||
void updateMetavoxels(float deltaTime);
|
void updateMetavoxels(float deltaTime);
|
||||||
void updateCamera(float deltaTime);
|
void updateCamera(float deltaTime);
|
||||||
void updateDialogs(float deltaTime);
|
void updateDialogs(float deltaTime);
|
||||||
|
@ -352,8 +352,6 @@ private:
|
||||||
|
|
||||||
Stars _stars;
|
Stars _stars;
|
||||||
|
|
||||||
Cloud _cloud;
|
|
||||||
|
|
||||||
VoxelSystem _voxels;
|
VoxelSystem _voxels;
|
||||||
VoxelTree _clipboard; // if I copy/paste
|
VoxelTree _clipboard; // if I copy/paste
|
||||||
VoxelImporter* _voxelImporter;
|
VoxelImporter* _voxelImporter;
|
||||||
|
|
|
@ -37,6 +37,8 @@ static const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_LENGTH_MSECS * NUM_AUDI
|
||||||
|
|
||||||
static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / (float)SAMPLE_RATE * 1000.0;
|
static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / (float)SAMPLE_RATE * 1000.0;
|
||||||
|
|
||||||
|
static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300;
|
||||||
|
|
||||||
// Mute icon configration
|
// Mute icon configration
|
||||||
static const int ICON_SIZE = 24;
|
static const int ICON_SIZE = 24;
|
||||||
static const int ICON_LEFT = 0;
|
static const int ICON_LEFT = 0;
|
||||||
|
@ -65,7 +67,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
|
||||||
_measuredJitter(0),
|
_measuredJitter(0),
|
||||||
_jitterBufferSamples(initialJitterBufferSamples),
|
_jitterBufferSamples(initialJitterBufferSamples),
|
||||||
_lastInputLoudness(0),
|
_lastInputLoudness(0),
|
||||||
_averageInputLoudness(0),
|
_noiseGateMeasuredFloor(0),
|
||||||
|
_noiseGateSampleCounter(0),
|
||||||
_noiseGateOpen(false),
|
_noiseGateOpen(false),
|
||||||
_noiseGateEnabled(true),
|
_noiseGateEnabled(true),
|
||||||
_noiseGateFramesToClose(0),
|
_noiseGateFramesToClose(0),
|
||||||
|
@ -82,6 +85,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
|
||||||
{
|
{
|
||||||
// clear the array of locally injected samples
|
// clear the array of locally injected samples
|
||||||
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||||
|
// Create the noise sample array
|
||||||
|
_noiseSampleFrames = new float[NUMBER_OF_NOISE_SAMPLE_FRAMES];
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::init(QGLWidget *parent) {
|
void Audio::init(QGLWidget *parent) {
|
||||||
|
@ -351,26 +356,66 @@ void Audio::handleAudioInput() {
|
||||||
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
|
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
|
||||||
_inputFormat, _desiredInputFormat);
|
_inputFormat, _desiredInputFormat);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Impose Noise Gate
|
||||||
|
//
|
||||||
|
// The Noise Gate is used to reject constant background noise by measuring the noise
|
||||||
|
// floor observed at the microphone and then opening the 'gate' to allow microphone
|
||||||
|
// signals to be transmitted when the microphone samples average level exceeds a multiple
|
||||||
|
// of the noise floor.
|
||||||
|
//
|
||||||
|
// NOISE_GATE_HEIGHT: How loud you have to speak relative to noise background to open the gate.
|
||||||
|
// Make this value lower for more sensitivity and less rejection of noise.
|
||||||
|
// NOISE_GATE_WIDTH: The number of samples in an audio frame for which the height must be exceeded
|
||||||
|
// to open the gate.
|
||||||
|
// NOISE_GATE_CLOSE_FRAME_DELAY: Once the noise is below the gate height for the frame, how many frames
|
||||||
|
// will we wait before closing the gate.
|
||||||
|
// NOISE_GATE_FRAMES_TO_AVERAGE: How many audio frames should we average together to compute noise floor.
|
||||||
|
// More means better rejection but also can reject continuous things like singing.
|
||||||
|
// NUMBER_OF_NOISE_SAMPLE_FRAMES: How often should we re-evaluate the noise floor?
|
||||||
|
|
||||||
|
|
||||||
float loudness = 0;
|
float loudness = 0;
|
||||||
float thisSample = 0;
|
float thisSample = 0;
|
||||||
int samplesOverNoiseGate = 0;
|
int samplesOverNoiseGate = 0;
|
||||||
|
|
||||||
const float NOISE_GATE_HEIGHT = 3.f;
|
const float NOISE_GATE_HEIGHT = 7.f;
|
||||||
const int NOISE_GATE_WIDTH = 5;
|
const int NOISE_GATE_WIDTH = 5;
|
||||||
const int NOISE_GATE_CLOSE_FRAME_DELAY = 30;
|
const int NOISE_GATE_CLOSE_FRAME_DELAY = 5;
|
||||||
|
const int NOISE_GATE_FRAMES_TO_AVERAGE = 5;
|
||||||
|
|
||||||
for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
|
for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
|
||||||
thisSample = fabsf(monoAudioSamples[i]);
|
thisSample = fabsf(monoAudioSamples[i]);
|
||||||
loudness += thisSample;
|
loudness += thisSample;
|
||||||
// Noise Reduction: Count peaks above the average loudness
|
// Noise Reduction: Count peaks above the average loudness
|
||||||
if (thisSample > (_averageInputLoudness * NOISE_GATE_HEIGHT)) {
|
if (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT)) {
|
||||||
samplesOverNoiseGate++;
|
samplesOverNoiseGate++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_lastInputLoudness = loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
|
_lastInputLoudness = loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
|
||||||
const float LOUDNESS_AVERAGING_FRAMES = 1000.f; // This will be about 10 seconds
|
|
||||||
_averageInputLoudness = (1.f - 1.f / LOUDNESS_AVERAGING_FRAMES) * _averageInputLoudness + (1.f / LOUDNESS_AVERAGING_FRAMES) * _lastInputLoudness;
|
|
||||||
|
|
||||||
|
float averageOfAllSampleFrames = 0.f;
|
||||||
|
_noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness;
|
||||||
|
if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) {
|
||||||
|
float smallestSample = FLT_MAX;
|
||||||
|
for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i+= NOISE_GATE_FRAMES_TO_AVERAGE) {
|
||||||
|
float thisAverage = 0.0f;
|
||||||
|
for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) {
|
||||||
|
thisAverage += _noiseSampleFrames[j];
|
||||||
|
averageOfAllSampleFrames += _noiseSampleFrames[j];
|
||||||
|
}
|
||||||
|
thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE;
|
||||||
|
|
||||||
|
if (thisAverage < smallestSample) {
|
||||||
|
smallestSample = thisAverage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES;
|
||||||
|
_noiseGateMeasuredFloor = smallestSample;
|
||||||
|
_noiseGateSampleCounter = 0;
|
||||||
|
//qDebug("smallest sample = %.1f, avg of all = %.1f", _noiseGateMeasuredFloor, averageOfAllSampleFrames);
|
||||||
|
}
|
||||||
|
|
||||||
if (_noiseGateEnabled) {
|
if (_noiseGateEnabled) {
|
||||||
if (samplesOverNoiseGate > NOISE_GATE_WIDTH) {
|
if (samplesOverNoiseGate > NOISE_GATE_WIDTH) {
|
||||||
_noiseGateOpen = true;
|
_noiseGateOpen = true;
|
||||||
|
@ -487,7 +532,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
||||||
if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) {
|
if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) {
|
||||||
// we don't have any audio data left in the output buffer
|
// we don't have any audio data left in the output buffer
|
||||||
// we just starved
|
// we just starved
|
||||||
qDebug() << "Audio output just starved.";
|
//qDebug() << "Audio output just starved.";
|
||||||
_ringBuffer.setIsStarved(true);
|
_ringBuffer.setIsStarved(true);
|
||||||
_numFramesDisplayStarve = 10;
|
_numFramesDisplayStarve = 10;
|
||||||
}
|
}
|
||||||
|
@ -505,7 +550,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
||||||
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
|
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
|
||||||
+ (_jitterBufferSamples * 2))) {
|
+ (_jitterBufferSamples * 2))) {
|
||||||
// starved and we don't have enough to start, keep waiting
|
// starved and we don't have enough to start, keep waiting
|
||||||
qDebug() << "Buffer is starved and doesn't have enough samples to start. Held back.";
|
//qDebug() << "Buffer is starved and doesn't have enough samples to start. Held back.";
|
||||||
} else {
|
} else {
|
||||||
// We are either already playing back, or we have enough audio to start playing back.
|
// We are either already playing back, or we have enough audio to start playing back.
|
||||||
_ringBuffer.setIsStarved(false);
|
_ringBuffer.setIsStarved(false);
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
|
|
||||||
void render(int screenWidth, int screenHeight);
|
void render(int screenWidth, int screenHeight);
|
||||||
|
|
||||||
float getLastInputLoudness() const { return glm::max(_lastInputLoudness - _averageInputLoudness, 0.f); }
|
float getLastInputLoudness() const { return glm::max(_lastInputLoudness - _noiseGateMeasuredFloor, 0.f); }
|
||||||
|
|
||||||
void setNoiseGateEnabled(bool noiseGateEnabled) { _noiseGateEnabled = noiseGateEnabled; }
|
void setNoiseGateEnabled(bool noiseGateEnabled) { _noiseGateEnabled = noiseGateEnabled; }
|
||||||
|
|
||||||
|
@ -109,7 +109,9 @@ private:
|
||||||
float _measuredJitter;
|
float _measuredJitter;
|
||||||
int16_t _jitterBufferSamples;
|
int16_t _jitterBufferSamples;
|
||||||
float _lastInputLoudness;
|
float _lastInputLoudness;
|
||||||
float _averageInputLoudness;
|
float _noiseGateMeasuredFloor;
|
||||||
|
float* _noiseSampleFrames;
|
||||||
|
int _noiseGateSampleCounter;
|
||||||
bool _noiseGateOpen;
|
bool _noiseGateOpen;
|
||||||
bool _noiseGateEnabled;
|
bool _noiseGateEnabled;
|
||||||
int _noiseGateFramesToClose;
|
int _noiseGateFramesToClose;
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
//
|
|
||||||
// Cloud.cpp
|
|
||||||
// interface
|
|
||||||
//
|
|
||||||
// Created by Philip Rosedale on 11/17/12.
|
|
||||||
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <InterfaceConfig.h>
|
|
||||||
#include "Cloud.h"
|
|
||||||
#include "Util.h"
|
|
||||||
#include "Field.h"
|
|
||||||
|
|
||||||
const int NUM_PARTICLES = 100000;
|
|
||||||
const float FIELD_COUPLE = 0.001f;
|
|
||||||
const bool RENDER_FIELD = false;
|
|
||||||
|
|
||||||
Cloud::Cloud() {
|
|
||||||
glm::vec3 box = glm::vec3(PARTICLE_WORLD_SIZE);
|
|
||||||
_bounds = box;
|
|
||||||
_count = NUM_PARTICLES;
|
|
||||||
_particles = new Particle[_count];
|
|
||||||
_field = new Field(PARTICLE_WORLD_SIZE, FIELD_COUPLE);
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < _count; i++) {
|
|
||||||
_particles[i].position = randVector() * box;
|
|
||||||
const float INIT_VEL_SCALE = 0.03f;
|
|
||||||
_particles[i].velocity = randVector() * ((float)PARTICLE_WORLD_SIZE * INIT_VEL_SCALE);
|
|
||||||
_particles[i].color = randVector();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cloud::render() {
|
|
||||||
if (RENDER_FIELD) {
|
|
||||||
_field->render();
|
|
||||||
}
|
|
||||||
|
|
||||||
glPointSize(3.0f);
|
|
||||||
glDisable(GL_TEXTURE_2D);
|
|
||||||
glEnable(GL_POINT_SMOOTH);
|
|
||||||
|
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
|
||||||
glEnableClientState(GL_COLOR_ARRAY);
|
|
||||||
glVertexPointer(3, GL_FLOAT, 3 * sizeof(glm::vec3), &_particles[0].position);
|
|
||||||
glColorPointer(3, GL_FLOAT, 3 * sizeof(glm::vec3), &_particles[0].color);
|
|
||||||
glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);
|
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
|
||||||
glDisableClientState(GL_COLOR_ARRAY);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cloud::simulate (float deltaTime) {
|
|
||||||
unsigned int i;
|
|
||||||
_field->simulate(deltaTime);
|
|
||||||
for (i = 0; i < _count; ++i) {
|
|
||||||
|
|
||||||
// Update position
|
|
||||||
_particles[i].position += _particles[i].velocity * deltaTime;
|
|
||||||
|
|
||||||
// Decay Velocity (Drag)
|
|
||||||
const float CONSTANT_DAMPING = 0.15f;
|
|
||||||
_particles[i].velocity *= (1.f - CONSTANT_DAMPING * deltaTime);
|
|
||||||
|
|
||||||
// Interact with Field
|
|
||||||
_field->interact(deltaTime, _particles[i].position, _particles[i].velocity);
|
|
||||||
|
|
||||||
// Update color to velocity
|
|
||||||
_particles[i].color = (glm::normalize(_particles[i].velocity) * 0.5f) + 0.5f;
|
|
||||||
|
|
||||||
// Bounce at bounds
|
|
||||||
if ((_particles[i].position.x > _bounds.x) || (_particles[i].position.x < 0.f)) {
|
|
||||||
_particles[i].position.x = glm::clamp(_particles[i].position.x, 0.f, _bounds.x);
|
|
||||||
_particles[i].velocity.x *= -1.f;
|
|
||||||
}
|
|
||||||
if ((_particles[i].position.y > _bounds.y) || (_particles[i].position.y < 0.f)) {
|
|
||||||
_particles[i].position.y = glm::clamp(_particles[i].position.y, 0.f, _bounds.y);
|
|
||||||
_particles[i].velocity.y *= -1.f;
|
|
||||||
}
|
|
||||||
if ((_particles[i].position.z > _bounds.z) || (_particles[i].position.z < 0.f)) {
|
|
||||||
_particles[i].position.z = glm::clamp(_particles[i].position.z, 0.f, _bounds.z);
|
|
||||||
_particles[i].velocity.z *= -1.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// Cloud.h
|
|
||||||
// interface
|
|
||||||
//
|
|
||||||
// Created by Philip Rosedale on 11/17/12.
|
|
||||||
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef __interface__Cloud__
|
|
||||||
#define __interface__Cloud__
|
|
||||||
|
|
||||||
#include "Field.h"
|
|
||||||
|
|
||||||
#define PARTICLE_WORLD_SIZE 256.0
|
|
||||||
|
|
||||||
class Cloud {
|
|
||||||
public:
|
|
||||||
Cloud();
|
|
||||||
void simulate(float deltaTime);
|
|
||||||
void render();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Particle {
|
|
||||||
glm::vec3 position, velocity, color;
|
|
||||||
}* _particles;
|
|
||||||
|
|
||||||
unsigned int _count;
|
|
||||||
glm::vec3 _bounds;
|
|
||||||
Field* _field;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -93,6 +93,8 @@ Menu::Menu() :
|
||||||
|
|
||||||
addDisabledActionAndSeparator(fileMenu, "Scripts");
|
addDisabledActionAndSeparator(fileMenu, "Scripts");
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog()));
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog()));
|
||||||
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts()));
|
||||||
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts()));
|
||||||
_activeScriptsMenu = fileMenu->addMenu("Running Scripts");
|
_activeScriptsMenu = fileMenu->addMenu("Running Scripts");
|
||||||
|
|
||||||
addDisabledActionAndSeparator(fileMenu, "Voxels");
|
addDisabledActionAndSeparator(fileMenu, "Voxels");
|
||||||
|
@ -161,7 +163,7 @@ Menu::Menu() :
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
addDisabledActionAndSeparator(editMenu, "Physics");
|
addDisabledActionAndSeparator(editMenu, "Physics");
|
||||||
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, true);
|
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false);
|
||||||
|
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
|
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
|
||||||
|
@ -238,9 +240,9 @@ Menu::Menu() :
|
||||||
SLOT(setFullscreen(bool)));
|
SLOT(setFullscreen(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true,
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true,
|
||||||
appInstance,SLOT(cameraMenuChanged()));
|
appInstance,SLOT(cameraMenuChanged()));
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H);
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true);
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H,false,
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false,
|
||||||
appInstance,SLOT(cameraMenuChanged()));
|
appInstance, SLOT(cameraMenuChanged()));
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0,
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0,
|
||||||
false,
|
false,
|
||||||
|
@ -266,14 +268,8 @@ Menu::Menu() :
|
||||||
appInstance->getAvatar(),
|
appInstance->getAvatar(),
|
||||||
SLOT(resetSize()));
|
SLOT(resetSize()));
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu,
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::OffAxisProjection, 0, false);
|
||||||
MenuOption::OffAxisProjection,
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false);
|
||||||
0,
|
|
||||||
true);
|
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu,
|
|
||||||
MenuOption::TurnWithHead,
|
|
||||||
0,
|
|
||||||
true);
|
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MoveWithLean, 0, false);
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MoveWithLean, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HeadMouse, 0, false);
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HeadMouse, 0, false);
|
||||||
|
|
||||||
|
@ -298,7 +294,6 @@ Menu::Menu() :
|
||||||
appInstance->getGlowEffect(),
|
appInstance->getGlowEffect(),
|
||||||
SLOT(cycleRenderMode()));
|
SLOT(cycleRenderMode()));
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ParticleCloud, 0, false);
|
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false);
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, false);
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, false);
|
||||||
|
|
||||||
|
@ -339,14 +334,14 @@ Menu::Menu() :
|
||||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true);
|
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true);
|
||||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollisionProxies);
|
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollisionProxies);
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, true);
|
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
|
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
|
||||||
MenuOption::FaceshiftTCP,
|
MenuOption::FaceshiftTCP,
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
appInstance->getFaceshift(),
|
appInstance->getFaceshift(),
|
||||||
SLOT(setTCPEnabled(bool)));
|
SLOT(setTCPEnabled(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, true);
|
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
|
||||||
|
|
||||||
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
|
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
|
||||||
|
|
||||||
|
@ -356,7 +351,7 @@ Menu::Menu() :
|
||||||
true,
|
true,
|
||||||
appInstance->getSixenseManager(),
|
appInstance->getSixenseManager(),
|
||||||
SLOT(setFilter(bool)));
|
SLOT(setFilter(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayLeapHands, 0, true);
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::VoxelDrumming, 0, false);
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::VoxelDrumming, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false);
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false);
|
||||||
|
|
|
@ -183,7 +183,7 @@ namespace MenuOption {
|
||||||
const QString DisableDeltaSending = "Disable Delta Sending";
|
const QString DisableDeltaSending = "Disable Delta Sending";
|
||||||
const QString DisableLowRes = "Disable Lower Resolution While Moving";
|
const QString DisableLowRes = "Disable Lower Resolution While Moving";
|
||||||
const QString DisplayFrustum = "Display Frustum";
|
const QString DisplayFrustum = "Display Frustum";
|
||||||
const QString DisplayLeapHands = "Display Leap Hands";
|
const QString DisplayHands = "Display Hands";
|
||||||
const QString DisplayHandTargets = "Display Hand Targets";
|
const QString DisplayHandTargets = "Display Hand Targets";
|
||||||
const QString FilterSixense = "Smooth Sixense Movement";
|
const QString FilterSixense = "Smooth Sixense Movement";
|
||||||
const QString DontRenderVoxels = "Don't call _voxels.render()";
|
const QString DontRenderVoxels = "Don't call _voxels.render()";
|
||||||
|
@ -223,7 +223,6 @@ namespace MenuOption {
|
||||||
const QString KillLocalVoxels = "Kill Local Voxels";
|
const QString KillLocalVoxels = "Kill Local Voxels";
|
||||||
const QString GoHome = "Go Home";
|
const QString GoHome = "Go Home";
|
||||||
const QString Gravity = "Use Gravity";
|
const QString Gravity = "Use Gravity";
|
||||||
const QString ParticleCloud = "Particle Cloud";
|
|
||||||
const QString LodTools = "LOD Tools";
|
const QString LodTools = "LOD Tools";
|
||||||
const QString Log = "Log";
|
const QString Log = "Log";
|
||||||
const QString Login = "Login";
|
const QString Login = "Login";
|
||||||
|
@ -244,8 +243,10 @@ namespace MenuOption {
|
||||||
const QString PasteVoxels = "Paste";
|
const QString PasteVoxels = "Paste";
|
||||||
const QString PasteToVoxel = "Paste to Voxel...";
|
const QString PasteToVoxel = "Paste to Voxel...";
|
||||||
const QString PipelineWarnings = "Show Render Pipeline Warnings";
|
const QString PipelineWarnings = "Show Render Pipeline Warnings";
|
||||||
|
const QString PlaySlaps = "Play Slaps";
|
||||||
const QString Preferences = "Preferences...";
|
const QString Preferences = "Preferences...";
|
||||||
const QString RandomizeVoxelColors = "Randomize Voxel TRUE Colors";
|
const QString RandomizeVoxelColors = "Randomize Voxel TRUE Colors";
|
||||||
|
const QString ReloadAllScripts = "Reload All Scripts";
|
||||||
const QString ResetAvatarSize = "Reset Avatar Size";
|
const QString ResetAvatarSize = "Reset Avatar Size";
|
||||||
const QString ResetSwatchColors = "Reset Swatch Colors";
|
const QString ResetSwatchColors = "Reset Swatch Colors";
|
||||||
const QString RunTimingTests = "Run Timing Tests";
|
const QString RunTimingTests = "Run Timing Tests";
|
||||||
|
@ -254,11 +255,10 @@ namespace MenuOption {
|
||||||
const QString SettingsExport = "Export Settings";
|
const QString SettingsExport = "Export Settings";
|
||||||
const QString ShowAllLocalVoxels = "Show All Local Voxels";
|
const QString ShowAllLocalVoxels = "Show All Local Voxels";
|
||||||
const QString ShowTrueColors = "Show TRUE Colors";
|
const QString ShowTrueColors = "Show TRUE Colors";
|
||||||
const QString VoxelDrumming = "Voxel Drumming";
|
|
||||||
const QString PlaySlaps = "Play Slaps";
|
|
||||||
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
|
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
|
||||||
const QString Stars = "Stars";
|
const QString Stars = "Stars";
|
||||||
const QString Stats = "Stats";
|
const QString Stats = "Stats";
|
||||||
|
const QString StopAllScripts = "Stop All Scripts";
|
||||||
const QString TestPing = "Test Ping";
|
const QString TestPing = "Test Ping";
|
||||||
const QString TreeStats = "Calculate Tree Stats";
|
const QString TreeStats = "Calculate Tree Stats";
|
||||||
const QString TransmitterDrive = "Transmitter Drive";
|
const QString TransmitterDrive = "Transmitter Drive";
|
||||||
|
@ -269,6 +269,7 @@ namespace MenuOption {
|
||||||
const QString VoxelAddMode = "Add Voxel Mode";
|
const QString VoxelAddMode = "Add Voxel Mode";
|
||||||
const QString VoxelColorMode = "Color Voxel Mode";
|
const QString VoxelColorMode = "Color Voxel Mode";
|
||||||
const QString VoxelDeleteMode = "Delete Voxel Mode";
|
const QString VoxelDeleteMode = "Delete Voxel Mode";
|
||||||
|
const QString VoxelDrumming = "Voxel Drumming";
|
||||||
const QString VoxelGetColorMode = "Get Color Mode";
|
const QString VoxelGetColorMode = "Get Color Mode";
|
||||||
const QString VoxelMode = "Cycle Voxel Mode";
|
const QString VoxelMode = "Cycle Voxel Mode";
|
||||||
const QString VoxelPaintColor = "Voxel Paint Color";
|
const QString VoxelPaintColor = "Voxel Paint Color";
|
||||||
|
|
|
@ -162,6 +162,7 @@ void Avatar::render(bool forceRenderHead) {
|
||||||
// render body
|
// render body
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
|
||||||
_skeletonModel.renderCollisionProxies(1.f);
|
_skeletonModel.renderCollisionProxies(1.f);
|
||||||
|
//_head.getFaceModel().renderCollisionProxies(0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||||
|
@ -276,11 +277,14 @@ bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penet
|
||||||
bool didPenetrate = false;
|
bool didPenetrate = false;
|
||||||
glm::vec3 skeletonPenetration;
|
glm::vec3 skeletonPenetration;
|
||||||
ModelCollisionInfo collisionInfo;
|
ModelCollisionInfo collisionInfo;
|
||||||
|
/* Temporarily disabling collisions against the skeleton because the collision proxies up
|
||||||
|
* near the neck are bad and prevent the hand from hitting the face.
|
||||||
if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) {
|
if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) {
|
||||||
collisionInfo._model = &_skeletonModel;
|
collisionInfo._model = &_skeletonModel;
|
||||||
collisions.push_back(collisionInfo);
|
collisions.push_back(collisionInfo);
|
||||||
didPenetrate = true;
|
didPenetrate = true;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
|
if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
|
||||||
collisionInfo._model = &(_head.getFaceModel());
|
collisionInfo._model = &(_head.getFaceModel());
|
||||||
collisions.push_back(collisionInfo);
|
collisions.push_back(collisionInfo);
|
||||||
|
@ -445,15 +449,36 @@ float Avatar::getHeight() const {
|
||||||
return extents.maximum.y - extents.minimum.y;
|
return extents.maximum.y - extents.minimum.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Avatar::poke(ModelCollisionInfo& collision) {
|
bool Avatar::collisionWouldMoveAvatar(ModelCollisionInfo& collision) const {
|
||||||
// ATM poke() can only affect the Skeleton (not the head)
|
// ATM only the Skeleton is pokeable
|
||||||
// TODO: make poke affect head
|
// TODO: make poke affect head
|
||||||
|
if (!collision._model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
|
if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
|
||||||
return _skeletonModel.poke(collision);
|
// collision response of skeleton is temporarily disabled
|
||||||
|
return false;
|
||||||
|
//return _skeletonModel.collisionHitsMoveableJoint(collision);
|
||||||
|
}
|
||||||
|
if (collision._model == &(_head.getFaceModel())) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Avatar::applyCollision(ModelCollisionInfo& collision) {
|
||||||
|
if (!collision._model) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (collision._model == &(_head.getFaceModel())) {
|
||||||
|
_head.applyCollision(collision);
|
||||||
|
}
|
||||||
|
// TODO: make skeleton respond to collisions
|
||||||
|
//if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
|
||||||
|
// _skeletonModel.applyCollision(collision);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
float Avatar::getPelvisFloatingHeight() const {
|
float Avatar::getPelvisFloatingHeight() const {
|
||||||
return -_skeletonModel.getBindExtents().minimum.y;
|
return -_skeletonModel.getBindExtents().minimum.y;
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,9 +128,11 @@ public:
|
||||||
|
|
||||||
float getHeight() const;
|
float getHeight() const;
|
||||||
|
|
||||||
|
/// \return true if we expect the avatar would move as a result of the collision
|
||||||
|
bool collisionWouldMoveAvatar(ModelCollisionInfo& collision) const;
|
||||||
|
|
||||||
/// \param collision a data structure for storing info about collisions against Models
|
/// \param collision a data structure for storing info about collisions against Models
|
||||||
/// \return true if the collision affects the Avatar models
|
void applyCollision(ModelCollisionInfo& collision);
|
||||||
bool poke(ModelCollisionInfo& collision);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateCollisionFlags();
|
void updateCollisionFlags();
|
||||||
|
|
|
@ -57,7 +57,6 @@ void Hand::simulate(float deltaTime, bool isMine) {
|
||||||
|
|
||||||
if (isMine) {
|
if (isMine) {
|
||||||
_buckyBalls.simulate(deltaTime);
|
_buckyBalls.simulate(deltaTime);
|
||||||
updateCollisions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateGeometry();
|
calculateGeometry();
|
||||||
|
@ -126,91 +125,105 @@ void Hand::simulate(float deltaTime, bool isMine) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hand::updateCollisions() {
|
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
||||||
// use position to obtain the left and right palm indices
|
if (!avatar || avatar == _owningAvatar) {
|
||||||
int leftPalmIndex, rightPalmIndex;
|
// don't collide with our own hands (that is done elsewhere)
|
||||||
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
return;
|
||||||
|
}
|
||||||
ModelCollisionList collisions;
|
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
||||||
// check for collisions
|
|
||||||
for (size_t i = 0; i < getNumPalms(); i++) {
|
for (size_t i = 0; i < getNumPalms(); i++) {
|
||||||
PalmData& palm = getPalms()[i];
|
PalmData& palm = getPalms()[i];
|
||||||
if (!palm.isActive()) {
|
if (!palm.isActive()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
|
||||||
glm::vec3 totalPenetration;
|
glm::vec3 totalPenetration;
|
||||||
|
ModelCollisionList collisions;
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) {
|
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
|
||||||
// check other avatars
|
// Check for palm collisions
|
||||||
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
|
glm::vec3 myPalmPosition = palm.getPosition();
|
||||||
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
float palmCollisionDistance = 0.1f;
|
||||||
if (avatar == _owningAvatar) {
|
bool wasColliding = palm.getIsCollidingWithPalm();
|
||||||
// don't collid with our own hands
|
palm.setIsCollidingWithPalm(false);
|
||||||
|
// If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
|
||||||
|
for (size_t j = 0; j < avatar->getHand().getNumPalms(); j++) {
|
||||||
|
PalmData& otherPalm = avatar->getHand().getPalms()[j];
|
||||||
|
if (!otherPalm.isActive()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
|
glm::vec3 otherPalmPosition = otherPalm.getPosition();
|
||||||
// Check for palm collisions
|
if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
|
||||||
glm::vec3 myPalmPosition = palm.getPosition();
|
palm.setIsCollidingWithPalm(true);
|
||||||
float palmCollisionDistance = 0.1f;
|
if (!wasColliding) {
|
||||||
bool wasColliding = palm.getIsCollidingWithPalm();
|
const float PALM_COLLIDE_VOLUME = 1.f;
|
||||||
palm.setIsCollidingWithPalm(false);
|
const float PALM_COLLIDE_FREQUENCY = 1000.f;
|
||||||
// If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
|
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
|
||||||
for (size_t j = 0; j < avatar->getHand().getNumPalms(); j++) {
|
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
|
||||||
PalmData& otherPalm = avatar->getHand().getPalms()[j];
|
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
|
||||||
if (!otherPalm.isActive()) {
|
PALM_COLLIDE_FREQUENCY,
|
||||||
continue;
|
PALM_COLLIDE_DURATION_MAX,
|
||||||
}
|
PALM_COLLIDE_DECAY_PER_SAMPLE);
|
||||||
glm::vec3 otherPalmPosition = otherPalm.getPosition();
|
// If the other person's palm is in motion, move mine downward to show I was hit
|
||||||
if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
|
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
|
||||||
palm.setIsCollidingWithPalm(true);
|
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
|
||||||
if (!wasColliding) {
|
// add slapback here
|
||||||
const float PALM_COLLIDE_VOLUME = 1.f;
|
|
||||||
const float PALM_COLLIDE_FREQUENCY = 1000.f;
|
|
||||||
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
|
|
||||||
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
|
|
||||||
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
|
|
||||||
PALM_COLLIDE_FREQUENCY,
|
|
||||||
PALM_COLLIDE_DURATION_MAX,
|
|
||||||
PALM_COLLIDE_DECAY_PER_SAMPLE);
|
|
||||||
// If the other person's palm is in motion, move mine downward to show I was hit
|
|
||||||
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
|
|
||||||
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
|
|
||||||
// add slapback here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) {
|
|
||||||
for (size_t j = 0; j < collisions.size(); ++j) {
|
|
||||||
if (!avatar->poke(collisions[j])) {
|
|
||||||
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) {
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
|
for (int j = 0; j < collisions.size(); ++j) {
|
||||||
// and the current avatar (ignoring everything below the parent of the parent of the last free joint)
|
if (isMyHand) {
|
||||||
collisions.clear();
|
if (!avatar->collisionWouldMoveAvatar(collisions[j])) {
|
||||||
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
|
// we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
|
||||||
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
|
// not expected to respond to the collision (hand hit unmovable part of their Avatar)
|
||||||
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
|
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
|
||||||
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
|
}
|
||||||
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) {
|
} else {
|
||||||
for (size_t j = 0; j < collisions.size(); ++j) {
|
// when !isMyHand then avatar is MyAvatar and we apply the collision
|
||||||
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
|
// which might not do anything (hand hit unmovable part of MyAvatar) however
|
||||||
|
// we don't resolve the hand's penetration because we expect the remote
|
||||||
|
// simulation to do the right thing.
|
||||||
|
avatar->applyCollision(collisions[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isMyHand) {
|
||||||
// un-penetrate
|
// resolve penetration
|
||||||
palm.addToPosition(-totalPenetration);
|
palm.addToPosition(-totalPenetration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we recycle the collisions container, so we clear it for the next loop
|
void Hand::collideAgainstOurself() {
|
||||||
|
if (!Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelCollisionList collisions;
|
||||||
|
int leftPalmIndex, rightPalmIndex;
|
||||||
|
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
||||||
|
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < getNumPalms(); i++) {
|
||||||
|
PalmData& palm = getPalms()[i];
|
||||||
|
if (!palm.isActive()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
glm::vec3 totalPenetration;
|
||||||
|
// and the current avatar (ignoring everything below the parent of the parent of the last free joint)
|
||||||
collisions.clear();
|
collisions.clear();
|
||||||
|
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
|
||||||
|
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
|
||||||
|
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
|
||||||
|
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
|
||||||
|
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) {
|
||||||
|
for (int j = 0; j < collisions.size(); ++j) {
|
||||||
|
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// resolve penetration
|
||||||
|
palm.addToPosition(-totalPenetration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +316,7 @@ void Hand::render(bool isMine) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayLeapHands)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) {
|
||||||
renderLeapHands(isMine);
|
renderLeapHands(isMine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,9 @@ public:
|
||||||
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
|
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
|
||||||
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
|
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
|
||||||
|
|
||||||
|
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
|
||||||
|
void collideAgainstOurself();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// disallow copies of the Hand, copy of owning Avatar is disallowed too
|
// disallow copies of the Hand, copy of owning Avatar is disallowed too
|
||||||
Hand(const Hand&);
|
Hand(const Hand&);
|
||||||
|
@ -87,7 +90,6 @@ private:
|
||||||
void renderLeapHands(bool isMine);
|
void renderLeapHands(bool isMine);
|
||||||
void renderLeapFingerTrails();
|
void renderLeapFingerTrails();
|
||||||
|
|
||||||
void updateCollisions();
|
|
||||||
void calculateGeometry();
|
void calculateGeometry();
|
||||||
|
|
||||||
void handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime);
|
void handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime);
|
||||||
|
|
|
@ -219,6 +219,34 @@ float Head::getTweakedRoll() const {
|
||||||
return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
|
return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
|
||||||
|
// HACK: the collision proxies for the FaceModel are bad. As a temporary workaround
|
||||||
|
// we collide against a hard coded collision proxy.
|
||||||
|
// TODO: get a better collision proxy here.
|
||||||
|
const float HEAD_RADIUS = 0.15f;
|
||||||
|
const glm::vec3 HEAD_CENTER = _position;
|
||||||
|
|
||||||
|
// collide the contactPoint against the collision proxy to obtain a new penetration
|
||||||
|
// NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere)
|
||||||
|
glm::vec3 penetration;
|
||||||
|
if (findPointSpherePenetration(collisionInfo._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
|
||||||
|
// compute lean angles
|
||||||
|
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
|
||||||
|
glm::quat bodyRotation = owningAvatar->getOrientation();
|
||||||
|
glm::vec3 neckPosition;
|
||||||
|
if (owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) {
|
||||||
|
glm::vec3 xAxis = bodyRotation * glm::vec3(1.f, 0.f, 0.f);
|
||||||
|
glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f);
|
||||||
|
float neckLength = glm::length(_position - neckPosition);
|
||||||
|
if (neckLength > 0.f) {
|
||||||
|
float forward = glm::dot(collisionInfo._penetration, zAxis) / neckLength;
|
||||||
|
float sideways = - glm::dot(collisionInfo._penetration, xAxis) / neckLength;
|
||||||
|
addLean(sideways, forward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Head::renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition) {
|
void Head::renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition) {
|
||||||
|
|
||||||
Application::getInstance()->getGlowEffect()->begin();
|
Application::getInstance()->getGlowEffect()->begin();
|
||||||
|
|
|
@ -79,6 +79,8 @@ public:
|
||||||
float getTweakedPitch() const;
|
float getTweakedPitch() const;
|
||||||
float getTweakedYaw() const;
|
float getTweakedYaw() const;
|
||||||
float getTweakedRoll() const;
|
float getTweakedRoll() const;
|
||||||
|
|
||||||
|
void applyCollision(ModelCollisionInfo& collisionInfo);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// disallow copies of the Head, copy of owning Avatar is disallowed too
|
// disallow copies of the Head, copy of owning Avatar is disallowed too
|
||||||
|
|
|
@ -112,23 +112,7 @@ void MyAvatar::updateTransmitter(float deltaTime) {
|
||||||
void MyAvatar::update(float deltaTime) {
|
void MyAvatar::update(float deltaTime) {
|
||||||
updateTransmitter(deltaTime);
|
updateTransmitter(deltaTime);
|
||||||
|
|
||||||
// TODO: resurrect touch interactions between avatars
|
updateFromGyros(deltaTime);
|
||||||
//// rotate body yaw for yaw received from multitouch
|
|
||||||
//setOrientation(getOrientation() * glm::quat(glm::vec3(0, _yawFromTouch, 0)));
|
|
||||||
//_yawFromTouch = 0.f;
|
|
||||||
//
|
|
||||||
//// apply pitch from touch
|
|
||||||
//_head.setPitch(_head.getPitch() + _pitchFromTouch);
|
|
||||||
//_pitchFromTouch = 0.0f;
|
|
||||||
//
|
|
||||||
//float TOUCH_YAW_SCALE = -0.25f;
|
|
||||||
//float TOUCH_PITCH_SCALE = -12.5f;
|
|
||||||
//float FIXED_TOUCH_TIMESTEP = 0.016f;
|
|
||||||
//_yawFromTouch += ((_touchAvgX - _lastTouchAvgX) * TOUCH_YAW_SCALE * FIXED_TOUCH_TIMESTEP);
|
|
||||||
//_pitchFromTouch += ((_touchAvgY - _lastTouchAvgY) * TOUCH_PITCH_SCALE * FIXED_TOUCH_TIMESTEP);
|
|
||||||
|
|
||||||
// Update my avatar's state from gyros
|
|
||||||
updateFromGyros(Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead));
|
|
||||||
|
|
||||||
// Update head mouse from faceshift if active
|
// Update head mouse from faceshift if active
|
||||||
Faceshift* faceshift = Application::getInstance()->getFaceshift();
|
Faceshift* faceshift = Application::getInstance()->getFaceshift();
|
||||||
|
@ -225,8 +209,6 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
updateCollisionWithVoxels(deltaTime, radius);
|
updateCollisionWithVoxels(deltaTime, radius);
|
||||||
}
|
}
|
||||||
if (_collisionFlags & COLLISION_GROUP_AVATARS) {
|
if (_collisionFlags & COLLISION_GROUP_AVATARS) {
|
||||||
// Note, hand-vs-avatar collisions are done elsewhere
|
|
||||||
// This is where we avatar-vs-avatar bounding capsule
|
|
||||||
updateCollisionWithAvatars(deltaTime);
|
updateCollisionWithAvatars(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +313,7 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
_position += _velocity * deltaTime;
|
_position += _velocity * deltaTime;
|
||||||
|
|
||||||
// update avatar skeleton and simulate hand and head
|
// update avatar skeleton and simulate hand and head
|
||||||
|
_hand.collideAgainstOurself();
|
||||||
_hand.simulate(deltaTime, true);
|
_hand.simulate(deltaTime, true);
|
||||||
_skeletonModel.simulate(deltaTime);
|
_skeletonModel.simulate(deltaTime);
|
||||||
_head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
|
_head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
|
||||||
|
@ -350,7 +333,7 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
const float MAX_PITCH = 90.0f;
|
const float MAX_PITCH = 90.0f;
|
||||||
|
|
||||||
// Update avatar head rotation with sensor data
|
// Update avatar head rotation with sensor data
|
||||||
void MyAvatar::updateFromGyros(bool turnWithHead) {
|
void MyAvatar::updateFromGyros(float deltaTime) {
|
||||||
Faceshift* faceshift = Application::getInstance()->getFaceshift();
|
Faceshift* faceshift = Application::getInstance()->getFaceshift();
|
||||||
glm::vec3 estimatedPosition, estimatedRotation;
|
glm::vec3 estimatedPosition, estimatedRotation;
|
||||||
|
|
||||||
|
@ -358,7 +341,7 @@ void MyAvatar::updateFromGyros(bool turnWithHead) {
|
||||||
estimatedPosition = faceshift->getHeadTranslation();
|
estimatedPosition = faceshift->getHeadTranslation();
|
||||||
estimatedRotation = safeEulerAngles(faceshift->getHeadRotation());
|
estimatedRotation = safeEulerAngles(faceshift->getHeadRotation());
|
||||||
// Rotate the body if the head is turned beyond the screen
|
// Rotate the body if the head is turned beyond the screen
|
||||||
if (turnWithHead) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) {
|
||||||
const float FACESHIFT_YAW_TURN_SENSITIVITY = 0.5f;
|
const float FACESHIFT_YAW_TURN_SENSITIVITY = 0.5f;
|
||||||
const float FACESHIFT_MIN_YAW_TURN = 15.f;
|
const float FACESHIFT_MIN_YAW_TURN = 15.f;
|
||||||
const float FACESHIFT_MAX_YAW_TURN = 50.f;
|
const float FACESHIFT_MAX_YAW_TURN = 50.f;
|
||||||
|
@ -373,11 +356,12 @@ void MyAvatar::updateFromGyros(bool turnWithHead) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// restore rotation, lean to neutral positions
|
// restore rotation, lean to neutral positions
|
||||||
const float RESTORE_RATE = 0.05f;
|
const float RESTORE_PERIOD = 1.f; // seconds
|
||||||
_head.setYaw(glm::mix(_head.getYaw(), 0.0f, RESTORE_RATE));
|
float restorePercentage = glm::clamp(deltaTime/RESTORE_PERIOD, 0.f, 1.f);
|
||||||
_head.setRoll(glm::mix(_head.getRoll(), 0.0f, RESTORE_RATE));
|
_head.setYaw(glm::mix(_head.getYaw(), 0.0f, restorePercentage));
|
||||||
_head.setLeanSideways(glm::mix(_head.getLeanSideways(), 0.0f, RESTORE_RATE));
|
_head.setRoll(glm::mix(_head.getRoll(), 0.0f, restorePercentage));
|
||||||
_head.setLeanForward(glm::mix(_head.getLeanForward(), 0.0f, RESTORE_RATE));
|
_head.setLeanSideways(glm::mix(_head.getLeanSideways(), 0.0f, restorePercentage));
|
||||||
|
_head.setLeanForward(glm::mix(_head.getLeanForward(), 0.0f, restorePercentage));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,7 +840,6 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
|
void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
|
||||||
const float VOXEL_ELASTICITY = 0.4f;
|
const float VOXEL_ELASTICITY = 0.4f;
|
||||||
const float VOXEL_DAMPING = 0.0f;
|
const float VOXEL_DAMPING = 0.0f;
|
||||||
|
@ -926,7 +909,43 @@ void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const float DEFAULT_HAND_RADIUS = 0.1f;
|
bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float heightA,
|
||||||
|
const glm::vec3 positionB, float radiusB, float heightB, glm::vec3& penetration) {
|
||||||
|
glm::vec3 positionBA = positionB - positionA;
|
||||||
|
float xzDistance = sqrt(positionBA.x * positionBA.x + positionBA.z * positionBA.z);
|
||||||
|
if (xzDistance < (radiusA + radiusB)) {
|
||||||
|
float yDistance = fabs(positionBA.y);
|
||||||
|
float halfHeights = 0.5 * (heightA + heightB);
|
||||||
|
if (yDistance < halfHeights) {
|
||||||
|
// cylinders collide
|
||||||
|
if (xzDistance > 0.f) {
|
||||||
|
positionBA.y = 0.f;
|
||||||
|
// note, penetration should point from A into B
|
||||||
|
penetration = positionBA * ((radiusA + radiusB - xzDistance) / xzDistance);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// exactly coaxial -- we'll return false for this case
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (yDistance < halfHeights + radiusA + radiusB) {
|
||||||
|
// caps collide
|
||||||
|
if (positionBA.y < 0.f) {
|
||||||
|
// A is above B
|
||||||
|
positionBA.y += halfHeights;
|
||||||
|
float BA = glm::length(positionBA);
|
||||||
|
penetration = positionBA * (radiusA + radiusB - BA) / BA;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// A is below B
|
||||||
|
positionBA.y -= halfHeights;
|
||||||
|
float BA = glm::length(positionBA);
|
||||||
|
penetration = positionBA * (radiusA + radiusB - BA) / BA;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
||||||
// Reset detector for nearest avatar
|
// Reset detector for nearest avatar
|
||||||
|
@ -936,7 +955,14 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
||||||
// no need to compute a bunch of stuff if we have one or fewer avatars
|
// no need to compute a bunch of stuff if we have one or fewer avatars
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float myRadius = getHeight();
|
float myBoundingRadius = 0.5f * getHeight();
|
||||||
|
|
||||||
|
// HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
|
||||||
|
// TODO: make the collision work without assuming avatar orientation
|
||||||
|
Extents myStaticExtents = _skeletonModel.getStaticExtents();
|
||||||
|
glm::vec3 staticScale = myStaticExtents.maximum - myStaticExtents.minimum;
|
||||||
|
float myCapsuleRadius = 0.25f * (staticScale.x + staticScale.z);
|
||||||
|
float myCapsuleHeight = staticScale.y;
|
||||||
|
|
||||||
CollisionInfo collisionInfo;
|
CollisionInfo collisionInfo;
|
||||||
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
|
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
|
||||||
|
@ -949,9 +975,25 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
||||||
if (_distanceToNearestAvatar > distance) {
|
if (_distanceToNearestAvatar > distance) {
|
||||||
_distanceToNearestAvatar = distance;
|
_distanceToNearestAvatar = distance;
|
||||||
}
|
}
|
||||||
float theirRadius = avatar->getHeight();
|
float theirBoundingRadius = 0.5f * avatar->getHeight();
|
||||||
if (distance < myRadius + theirRadius) {
|
if (distance < myBoundingRadius + theirBoundingRadius) {
|
||||||
// TODO: Andrew to make avatar-avatar capsule collisions work here
|
Extents theirStaticExtents = _skeletonModel.getStaticExtents();
|
||||||
|
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;
|
||||||
|
float theirCapsuleRadius = 0.25f * (staticScale.x + staticScale.z);
|
||||||
|
float theirCapsuleHeight = staticScale.y;
|
||||||
|
|
||||||
|
glm::vec3 penetration(0.f);
|
||||||
|
if (findAvatarAvatarPenetration(_position, myCapsuleRadius, myCapsuleHeight,
|
||||||
|
avatar->getPosition(), theirCapsuleRadius, theirCapsuleHeight, penetration)) {
|
||||||
|
// move the avatar out by half the penetration
|
||||||
|
setPosition(_position - 0.5f * penetration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// collide our hands against them
|
||||||
|
_hand.collideAgainstAvatar(avatar, true);
|
||||||
|
|
||||||
|
// collide their hands against us
|
||||||
|
avatar->getHand().collideAgainstAvatar(this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ public:
|
||||||
void reset();
|
void reset();
|
||||||
void update(float deltaTime);
|
void update(float deltaTime);
|
||||||
void simulate(float deltaTime);
|
void simulate(float deltaTime);
|
||||||
void updateFromGyros(bool turnWithHead);
|
void updateFromGyros(float deltaTime);
|
||||||
void updateTransmitter(float deltaTime);
|
void updateTransmitter(float deltaTime);
|
||||||
|
|
||||||
void render(bool forceRenderHead);
|
void render(bool forceRenderHead);
|
||||||
|
|
|
@ -36,23 +36,24 @@ void SkeletonModel::simulate(float deltaTime) {
|
||||||
HandData& hand = _owningAvatar->getHand();
|
HandData& hand = _owningAvatar->getHand();
|
||||||
hand.getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
hand.getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
||||||
|
|
||||||
const float HAND_RESTORATION_RATE = 0.25f;
|
const float HAND_RESTORATION_PERIOD = 1.f; // seconds
|
||||||
|
float handRestorePercent = glm::clamp(deltaTime / HAND_RESTORATION_PERIOD, 0.f, 1.f);
|
||||||
|
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
if (leftPalmIndex == -1) {
|
if (leftPalmIndex == -1) {
|
||||||
// no Leap data; set hands from mouse
|
// no Leap data; set hands from mouse
|
||||||
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
|
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
|
||||||
restoreRightHandPosition(HAND_RESTORATION_RATE);
|
restoreRightHandPosition(handRestorePercent);
|
||||||
} else {
|
} else {
|
||||||
applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition());
|
applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition());
|
||||||
}
|
}
|
||||||
restoreLeftHandPosition(HAND_RESTORATION_RATE);
|
restoreLeftHandPosition(handRestorePercent);
|
||||||
|
|
||||||
} else if (leftPalmIndex == rightPalmIndex) {
|
} else if (leftPalmIndex == rightPalmIndex) {
|
||||||
// right hand only
|
// right hand only
|
||||||
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
|
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
|
||||||
hand.getPalms()[leftPalmIndex]);
|
hand.getPalms()[leftPalmIndex]);
|
||||||
restoreLeftHandPosition(HAND_RESTORATION_RATE);
|
restoreLeftHandPosition(handRestorePercent);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices,
|
applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices,
|
||||||
|
|
|
@ -1274,6 +1274,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
|
|
||||||
geometry.bindExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
geometry.bindExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||||
geometry.bindExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
geometry.bindExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||||
|
geometry.staticExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||||
|
geometry.staticExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||||
|
|
||||||
QVariantHash springs = mapping.value("spring").toHash();
|
QVariantHash springs = mapping.value("spring").toHash();
|
||||||
QVariant defaultSpring = springs.value("default");
|
QVariant defaultSpring = springs.value("default");
|
||||||
|
@ -1430,6 +1432,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
boneDirection /= boneLength;
|
boneDirection /= boneLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool jointIsStatic = joint.freeLineage.isEmpty();
|
||||||
|
glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform);
|
||||||
float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix);
|
float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix);
|
||||||
float totalWeight = 0.0f;
|
float totalWeight = 0.0f;
|
||||||
for (int j = 0; j < cluster.indices.size(); j++) {
|
for (int j = 0; j < cluster.indices.size(); j++) {
|
||||||
|
@ -1447,6 +1451,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
joint.boneRadius = glm::max(joint.boneRadius, radiusScale * glm::distance(
|
joint.boneRadius = glm::max(joint.boneRadius, radiusScale * glm::distance(
|
||||||
vertex, boneEnd + boneDirection * proj));
|
vertex, boneEnd + boneDirection * proj));
|
||||||
}
|
}
|
||||||
|
if (jointIsStatic) {
|
||||||
|
// expand the extents of static (nonmovable) joints
|
||||||
|
geometry.staticExtents.minimum = glm::min(geometry.staticExtents.minimum, vertex + jointTranslation);
|
||||||
|
geometry.staticExtents.maximum = glm::max(geometry.staticExtents.maximum, vertex + jointTranslation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for an unused slot in the weights vector
|
// look for an unused slot in the weights vector
|
||||||
|
|
|
@ -159,6 +159,7 @@ public:
|
||||||
glm::vec3 neckPivot;
|
glm::vec3 neckPivot;
|
||||||
|
|
||||||
Extents bindExtents;
|
Extents bindExtents;
|
||||||
|
Extents staticExtents;
|
||||||
|
|
||||||
QVector<FBXAttachment> attachments;
|
QVector<FBXAttachment> attachments;
|
||||||
};
|
};
|
||||||
|
|
|
@ -305,6 +305,15 @@ Extents Model::getBindExtents() const {
|
||||||
return scaledExtents;
|
return scaledExtents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Extents Model::getStaticExtents() const {
|
||||||
|
if (!isActive()) {
|
||||||
|
return Extents();
|
||||||
|
}
|
||||||
|
const Extents& staticExtents = _geometry->getFBXGeometry().staticExtents;
|
||||||
|
Extents scaledExtents = { staticExtents.minimum * _scale, staticExtents.maximum * _scale };
|
||||||
|
return scaledExtents;
|
||||||
|
}
|
||||||
|
|
||||||
int Model::getParentJointIndex(int jointIndex) const {
|
int Model::getParentJointIndex(int jointIndex) const {
|
||||||
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).parentIndex : -1;
|
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).parentIndex : -1;
|
||||||
}
|
}
|
||||||
|
@ -468,7 +477,10 @@ bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetra
|
||||||
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
|
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
|
||||||
startRadius, endRadius, bonePenetration)) {
|
startRadius, endRadius, bonePenetration)) {
|
||||||
totalPenetration = addPenetrations(totalPenetration, bonePenetration);
|
totalPenetration = addPenetrations(totalPenetration, bonePenetration);
|
||||||
// TODO: Andrew to try to keep the joint furthest toward the root
|
// BUG: we currently overwrite the jointIndex with the last one found
|
||||||
|
// which can cause incorrect collisions when colliding against more than
|
||||||
|
// one joint.
|
||||||
|
// TODO: fix this.
|
||||||
jointIndex = i;
|
jointIndex = i;
|
||||||
}
|
}
|
||||||
outerContinue: ;
|
outerContinue: ;
|
||||||
|
@ -713,7 +725,19 @@ void Model::renderCollisionProxies(float alpha) {
|
||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::poke(ModelCollisionInfo& collision) {
|
bool Model::collisionHitsMoveableJoint(ModelCollisionInfo& collision) const {
|
||||||
|
// the joint is pokable by a collision if it exists and is free to move
|
||||||
|
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._jointIndex];
|
||||||
|
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// an empty freeLineage means the joint can't move
|
||||||
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
const QVector<int>& freeLineage = geometry.joints.at(collision._jointIndex).freeLineage;
|
||||||
|
return !freeLineage.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::applyCollision(ModelCollisionInfo& collision) {
|
||||||
// This needs work. At the moment it can wiggle joints that are free to move (such as arms)
|
// This needs work. At the moment it can wiggle joints that are free to move (such as arms)
|
||||||
// but unmovable joints (such as torso) cannot be influenced at all.
|
// but unmovable joints (such as torso) cannot be influenced at all.
|
||||||
glm::vec3 jointPosition(0.f);
|
glm::vec3 jointPosition(0.f);
|
||||||
|
@ -737,11 +761,10 @@ bool Model::poke(ModelCollisionInfo& collision) {
|
||||||
getJointPosition(jointIndex, end);
|
getJointPosition(jointIndex, end);
|
||||||
glm::vec3 newEnd = start + glm::angleAxis(glm::degrees(angle), axis) * (end - start);
|
glm::vec3 newEnd = start + glm::angleAxis(glm::degrees(angle), axis) * (end - start);
|
||||||
// try to move it
|
// try to move it
|
||||||
return setJointPosition(jointIndex, newEnd, -1, true);
|
setJointPosition(jointIndex, newEnd, -1, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::deleteGeometry() {
|
void Model::deleteGeometry() {
|
||||||
|
|
|
@ -66,6 +66,9 @@ public:
|
||||||
|
|
||||||
/// Returns the extents of the model in its bind pose.
|
/// Returns the extents of the model in its bind pose.
|
||||||
Extents getBindExtents() const;
|
Extents getBindExtents() const;
|
||||||
|
|
||||||
|
/// Returns the extents of the unmovable joints of the model.
|
||||||
|
Extents getStaticExtents() const;
|
||||||
|
|
||||||
/// Returns a reference to the shared geometry.
|
/// Returns a reference to the shared geometry.
|
||||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||||
|
@ -164,9 +167,12 @@ public:
|
||||||
|
|
||||||
void renderCollisionProxies(float alpha);
|
void renderCollisionProxies(float alpha);
|
||||||
|
|
||||||
|
/// \return true if the collision is against a moveable joint
|
||||||
|
bool collisionHitsMoveableJoint(ModelCollisionInfo& collision) const;
|
||||||
|
|
||||||
/// \param collisionInfo info about the collision
|
/// \param collisionInfo info about the collision
|
||||||
/// \return true if collision affects the Model
|
/// Use the collisionInfo to affect the model
|
||||||
bool poke(ModelCollisionInfo& collisionInfo);
|
void applyCollision(ModelCollisionInfo& collisionInfo);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// AvatarHashMap.cpp
|
// AvatarHashMap.cpp
|
||||||
// hifi
|
// hifi
|
||||||
//
|
//
|
||||||
// Created by Stephen AndrewMeadows on 1/28/2014.
|
// Created by AndrewMeadows on 1/28/2014.
|
||||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
|
@ -45,10 +45,3 @@ void HeadData::addLean(float sideways, float forwards) {
|
||||||
_leanForward += forwards;
|
_leanForward += forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HeadData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration) const {
|
|
||||||
// we would like to update this to determine collisions/penetrations with the Avatar's head sphere...
|
|
||||||
// but right now it does not appear as if the HeadData has a position and radius.
|
|
||||||
// this is a placeholder for now.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -58,13 +58,6 @@ public:
|
||||||
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
||||||
|
|
||||||
friend class AvatarData;
|
friend class AvatarData;
|
||||||
|
|
||||||
/// Checks for penetration between the described sphere and the hand.
|
|
||||||
/// \param penetratorCenter the center of the penetration test sphere
|
|
||||||
/// \param penetratorRadius the radius of the penetration test sphere
|
|
||||||
/// \param penetration[out] the vector in which to store the penetration
|
|
||||||
/// \return whether or not the sphere penetrated
|
|
||||||
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration) const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float _yaw;
|
float _yaw;
|
||||||
|
|
|
@ -11,13 +11,19 @@
|
||||||
#include "OctreeScriptingInterface.h"
|
#include "OctreeScriptingInterface.h"
|
||||||
|
|
||||||
OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packetSender,
|
OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packetSender,
|
||||||
JurisdictionListener* jurisdictionListener)
|
JurisdictionListener* jurisdictionListener) :
|
||||||
|
_packetSender(NULL),
|
||||||
|
_jurisdictionListener(NULL),
|
||||||
|
_managedPacketSender(false),
|
||||||
|
_managedJurisdictionListener(false),
|
||||||
|
_initialized(false)
|
||||||
{
|
{
|
||||||
setPacketSender(packetSender);
|
setPacketSender(packetSender);
|
||||||
setJurisdictionListener(jurisdictionListener);
|
setJurisdictionListener(jurisdictionListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
OctreeScriptingInterface::~OctreeScriptingInterface() {
|
OctreeScriptingInterface::~OctreeScriptingInterface() {
|
||||||
|
qDebug() << "OctreeScriptingInterface::~OctreeScriptingInterface() this=" << this;
|
||||||
cleanupManagedObjects();
|
cleanupManagedObjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +51,9 @@ void OctreeScriptingInterface::setJurisdictionListener(JurisdictionListener* jur
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeScriptingInterface::init() {
|
void OctreeScriptingInterface::init() {
|
||||||
|
if (_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_jurisdictionListener) {
|
if (_jurisdictionListener) {
|
||||||
_managedJurisdictionListener = false;
|
_managedJurisdictionListener = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,5 +73,5 @@ void OctreeScriptingInterface::init() {
|
||||||
if (QCoreApplication::instance()) {
|
if (QCoreApplication::instance()) {
|
||||||
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(cleanupManagedObjects()));
|
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(cleanupManagedObjects()));
|
||||||
}
|
}
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,7 @@ protected:
|
||||||
JurisdictionListener* _jurisdictionListener;
|
JurisdictionListener* _jurisdictionListener;
|
||||||
bool _managedPacketSender;
|
bool _managedPacketSender;
|
||||||
bool _managedJurisdictionListener;
|
bool _managedJurisdictionListener;
|
||||||
|
bool _initialized;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__OctreeScriptingInterface__) */
|
#endif /* defined(__hifi__OctreeScriptingInterface__) */
|
||||||
|
|
|
@ -31,6 +31,4 @@ public slots:
|
||||||
glm::quat angleAxis(float angle, const glm::vec3& v);
|
glm::quat angleAxis(float angle, const glm::vec3& v);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(__hifi__Quat__) */
|
#endif /* defined(__hifi__Quat__) */
|
||||||
|
|
|
@ -26,7 +26,9 @@ glm::vec3 Vec3::multiplyQbyV(const glm::quat& q, const glm::vec3& v) {
|
||||||
glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& v2) {
|
glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& v2) {
|
||||||
return v1 + v2;
|
return v1 + v2;
|
||||||
}
|
}
|
||||||
|
glm::vec3 Vec3::subtract(const glm::vec3& v1, const glm::vec3& v2) {
|
||||||
|
return v1 - v2;
|
||||||
|
}
|
||||||
float Vec3::length(const glm::vec3& v) {
|
float Vec3::length(const glm::vec3& v) {
|
||||||
return glm::length(v);
|
return glm::length(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ public slots:
|
||||||
glm::vec3 multiply(const glm::vec3& v1, float f);
|
glm::vec3 multiply(const glm::vec3& v1, float f);
|
||||||
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
|
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
|
||||||
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
|
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
|
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
float length(const glm::vec3& v);
|
float length(const glm::vec3& v);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue