diff --git a/cmake/modules/MacOSXBundleInfo.plist.in b/cmake/modules/MacOSXBundleInfo.plist.in
new file mode 100644
index 0000000000..1682b6c022
--- /dev/null
+++ b/cmake/modules/MacOSXBundleInfo.plist.in
@@ -0,0 +1,47 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ ${MACOSX_BUNDLE_EXECUTABLE_NAME}
+ CFBundleGetInfoString
+ ${MACOSX_BUNDLE_INFO_STRING}
+ CFBundleIconFile
+ ${MACOSX_BUNDLE_ICON_FILE}
+ CFBundleIdentifier
+ ${MACOSX_BUNDLE_GUI_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleLongVersionString
+ ${MACOSX_BUNDLE_LONG_VERSION_STRING}
+ CFBundleName
+ ${MACOSX_BUNDLE_BUNDLE_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ ${MACOSX_BUNDLE_SHORT_VERSION_STRING}
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ ${MACOSX_BUNDLE_BUNDLE_VERSION}
+ CSResourcesFileMapped
+
+ LSRequiresCarbon
+
+ NSHumanReadableCopyright
+ ${MACOSX_BUNDLE_COPYRIGHT}
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ ${MACOSX_BUNDLE_BUNDLE_NAME} URL
+ CFBundleURLSchemes
+
+ hifi
+
+
+
+
+
diff --git a/examples/cameraExample.js b/examples/cameraExample.js
index d42c3c1b0e..d55f376b76 100644
--- a/examples/cameraExample.js
+++ b/examples/cameraExample.js
@@ -69,6 +69,12 @@ function checkCamera() {
var viewJoystickPosition = Controller.getJoystickPosition(VIEW_CONTROLLER);
yaw -= viewJoystickPosition.x * JOYSTICK_YAW_MAG * deltaTime;
pitch += viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime;
+ if (yaw > 360) {
+ yaw -= 360;
+ }
+ if (yaw < -360) {
+ yaw += 360;
+ }
var orientation = Quat.fromPitchYawRoll(pitch, yaw, roll);
Camera.setOrientation(orientation);
}
diff --git a/examples/editVoxels.js b/examples/editVoxels.js
index 96bb09e6d6..c1f0c8dc49 100644
--- a/examples/editVoxels.js
+++ b/examples/editVoxels.js
@@ -7,9 +7,11 @@
//
// Captures mouse clicks and edits voxels accordingly.
//
-// click = create a new voxel on this face, same color as old
-// Alt + click = delete this voxel
+// click = create a new voxel on this face, same color as old (default color picker state)
+// right click or control + click = delete 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
//
@@ -18,15 +20,41 @@ function vLength(v) {
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_shift = 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 lastVoxelColor = { red: 0, green: 0, blue: 0 };
var lastVoxelScale = 0;
var dragStart = { x: 0, y: 0 };
+var mouseX = 0;
+var mouseY = 0;
+
+
+
// Create a table of the different colors you can choose
var colors = new Array();
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[6] = { red: 124, green: 36, blue: 36 };
colors[7] = { red: 63, green: 35, blue: 19 };
-var numColors = 6;
-var whichColor = 0;
+var numColors = 8;
+var whichColor = -1; // Starting color is 'Copy' mode
// 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 deleteSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Bubbles/bubbles1.raw");
-var changeColorSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Electronic/ElectronicBurst6.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/Voxels/voxel+delete.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();
+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) {
+ mouseX = event.x;
+ mouseY = event.y;
var pickRay = Camera.computePickRay(event.x, event.y);
var intersection = Voxels.findRayIntersection(pickRay);
- audioOptions.volume = 1.0;
- audioOptions.position = { x: intersection.voxel.x, y: intersection.voxel.y, z: intersection.voxel.z };
-
+ audioOptions.position = Vec3.sum(pickRay.origin, pickRay.direction);
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
Voxels.eraseVoxel(intersection.voxel.x, intersection.voxel.y, intersection.voxel.z, intersection.voxel.s);
Audio.playSound(deleteSound, audioOptions);
- } else if (key_shift) {
+ } else if (event.isShifted) {
// Recolor Voxel
- whichColor++;
- if (whichColor == numColors) whichColor = 0;
Voxels.setVoxel(intersection.voxel.x,
intersection.voxel.y,
intersection.voxel.z,
@@ -70,7 +137,9 @@ function mousePressEvent(event) {
Audio.playSound(changeColorSound, audioOptions);
} else {
// Add voxel on face
- var newVoxel = {
+ if (whichColor == -1) {
+ // Copy mode - use clicked voxel color
+ var newVoxel = {
x: intersection.voxel.x,
y: intersection.voxel.y,
z: intersection.voxel.z,
@@ -78,6 +147,16 @@ function mousePressEvent(event) {
red: intersection.voxel.red,
green: intersection.voxel.green,
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") {
newVoxel.x -= newVoxel.s;
@@ -108,55 +187,113 @@ function mousePressEvent(event) {
function keyPressEvent(event) {
key_alt = event.isAlt;
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) {
key_alt = false;
key_shift = false;
}
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) {
- var pickRay = Camera.computePickRay(event.x, event.y);
- var lastVoxelDistance = { x: pickRay.origin.x - lastVoxelPosition.x,
+ // Watch the drag direction to tell which way to 'extrude' this voxel
+ if (!isExtruding) {
+ var pickRay = Camera.computePickRay(event.x, event.y);
+ var lastVoxelDistance = { x: pickRay.origin.x - lastVoxelPosition.x,
y: pickRay.origin.y - lastVoxelPosition.y,
z: pickRay.origin.z - lastVoxelPosition.z };
- var distance = vLength(lastVoxelDistance);
- var mouseSpot = { x: pickRay.direction.x * distance, y: pickRay.direction.y * distance, z: pickRay.direction.z * distance };
- mouseSpot.x += pickRay.origin.x;
- mouseSpot.y += pickRay.origin.y;
- mouseSpot.z += pickRay.origin.z;
- var dx = mouseSpot.x - lastVoxelPosition.x;
- var dy = mouseSpot.y - lastVoxelPosition.y;
- var dz = mouseSpot.z - lastVoxelPosition.z;
- if (dx > lastVoxelScale) {
- lastVoxelPosition.x += lastVoxelScale;
- Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
- lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
- } else if (dx < -lastVoxelScale) {
- lastVoxelPosition.x -= lastVoxelScale;
- Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
- lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
- } else if (dy > lastVoxelScale) {
- lastVoxelPosition.y += lastVoxelScale;
- Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
- lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
- } else if (dy < -lastVoxelScale) {
- lastVoxelPosition.y -= lastVoxelScale;
- Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
- lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
- } else if (dz > lastVoxelScale) {
- lastVoxelPosition.z += lastVoxelScale;
- Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
- lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
- } else if (dz < -lastVoxelScale) {
- lastVoxelPosition.z -= lastVoxelScale;
- Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
- lastVoxelScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
+ var distance = vLength(lastVoxelDistance);
+ var mouseSpot = { x: pickRay.direction.x * distance, y: pickRay.direction.y * distance, z: pickRay.direction.z * distance };
+ mouseSpot.x += pickRay.origin.x;
+ mouseSpot.y += pickRay.origin.y;
+ mouseSpot.z += pickRay.origin.z;
+ var dx = mouseSpot.x - lastVoxelPosition.x;
+ var dy = mouseSpot.y - lastVoxelPosition.y;
+ var dz = mouseSpot.z - lastVoxelPosition.z;
+ extrudeScale = lastVoxelScale;
+ extrudeDirection = { x: 0, y: 0, z: 0 };
+ isExtruding = true;
+ if (dx > lastVoxelScale) extrudeDirection.x = extrudeScale;
+ else if (dx < -lastVoxelScale) extrudeDirection.x = -extrudeScale;
+ else if (dy > lastVoxelScale) extrudeDirection.y = extrudeScale;
+ else if (dy < -lastVoxelScale) extrudeDirection.y = -extrudeScale;
+ else if (dz > lastVoxelScale) extrudeDirection.z = extrudeScale;
+ else if (dz < -lastVoxelScale) extrudeDirection.z = -extrudeScale;
+ else isExtruding = false;
+ } else {
+ // We have got an extrusion direction, now look for mouse move beyond threshold to add new voxel
+ var dx = event.x - mouseX;
+ var dy = event.y - mouseY;
+ if (Math.sqrt(dx*dx + dy*dy) > PIXELS_PER_EXTRUDE_VOXEL) {
+ lastVoxelPosition = Vec3.sum(lastVoxelPosition, extrudeDirection);
+ Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z,
+ extrudeScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue);
+ mouseX = event.x;
+ mouseY = event.y;
+ }
}
}
}
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;
+ isOrbiting = false;
+ isExtruding = false;
}
Controller.mousePressEvent.connect(mousePressEvent);
diff --git a/examples/hydraMove.js b/examples/hydraMove.js
new file mode 100644
index 0000000000..6ce0ad884e
--- /dev/null
+++ b/examples/hydraMove.js
@@ -0,0 +1,210 @@
+//
+// hydraMove.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 2/10/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller and MyAvatar classes to implement
+// avatar flying through the hydra/controller joysticks
+//
+//
+
+var damping = 0.9;
+var position = { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z };
+var joysticksCaptured = false;
+var THRUST_CONTROLLER = 0;
+var VIEW_CONTROLLER = 1;
+var INITIAL_THRUST_MULTPLIER = 1.0;
+var THRUST_INCREASE_RATE = 1.05;
+var MAX_THRUST_MULTIPLIER = 75.0;
+var thrustMultiplier = INITIAL_THRUST_MULTPLIER;
+var grabDelta = { x: 0, y: 0, z: 0};
+var grabDeltaVelocity = { x: 0, y: 0, z: 0};
+var grabStartRotation = { x: 0, y: 0, z: 0, w: 1};
+var grabCurrentRotation = { x: 0, y: 0, z: 0, w: 1};
+var grabbingWithRightHand = false;
+var wasGrabbingWithRightHand = false;
+var grabbingWithLeftHand = false;
+var wasGrabbingWithLeftHand = false;
+var EPSILON = 0.000001;
+var velocity = { x: 0, y: 0, z: 0};
+var deltaTime = 1/60; // approximately our FPS - maybe better to be elapsed time since last call
+var THRUST_MAG_UP = 800.0;
+var THRUST_MAG_DOWN = 300.0;
+var THRUST_MAG_FWD = 500.0;
+var THRUST_MAG_BACK = 300.0;
+var THRUST_MAG_LATERAL = 250.0;
+var THRUST_JUMP = 120.0;
+
+var YAW_MAG = 500.0;
+var PITCH_MAG = 100.0;
+var THRUST_MAG_HAND_JETS = THRUST_MAG_FWD;
+var JOYSTICK_YAW_MAG = YAW_MAG;
+var JOYSTICK_PITCH_MAG = PITCH_MAG * 0.5;
+
+
+var LEFT_PALM = 0;
+var LEFT_BUTTON_4 = 4;
+var RIGHT_PALM = 2;
+var RIGHT_BUTTON_4 = 10;
+
+// Used by handleGrabBehavior() for managing the grab position changes
+function getAndResetGrabDelta() {
+ var HAND_GRAB_SCALE_DISTANCE = 2.0;
+ var delta = Vec3.multiply(grabDelta, (MyAvatar.scale * HAND_GRAB_SCALE_DISTANCE));
+ grabDelta = { x: 0, y: 0, z: 0};
+ var avatarRotation = MyAvatar.orientation;
+ var result = Vec3.multiplyQbyV(avatarRotation, Vec3.multiply(delta, -1));
+ return result;
+}
+
+// Used by handleGrabBehavior() for managing the grab velocity feature
+function getAndResetGrabDeltaVelocity() {
+ var HAND_GRAB_SCALE_VELOCITY = 50.0;
+ var delta = Vec3.multiply(grabDeltaVelocity, (MyAvatar.scale * HAND_GRAB_SCALE_VELOCITY));
+ grabDeltaVelocity = { x: 0, y: 0, z: 0};
+ var avatarRotation = MyAvatar.orientation;
+ var result = Quat.multiply(avatarRotation, Vec3.multiply(delta, -1));
+ return result;
+}
+
+// Used by handleGrabBehavior() for managing the grab rotation feature
+function getAndResetGrabRotation() {
+ var quatDiff = Quat.multiply(grabCurrentRotation, Quat.inverse(grabStartRotation));
+ grabStartRotation = grabCurrentRotation;
+ return quatDiff;
+}
+
+// handles all the grab related behavior: position (crawl), velocity (flick), and rotate (twist)
+function handleGrabBehavior() {
+ // check for and handle grab behaviors
+ grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_4);
+ grabbingWithLeftHand = Controller.isButtonPressed(LEFT_BUTTON_4);
+ stoppedGrabbingWithLeftHand = false;
+ stoppedGrabbingWithRightHand = false;
+
+ if (grabbingWithRightHand && !wasGrabbingWithRightHand) {
+ // Just starting grab, capture starting rotation
+ grabStartRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM);
+ }
+ if (grabbingWithRightHand) {
+ grabDelta = Vec3.sum(grabDelta, Vec3.multiply(Controller.getSpatialControlVelocity(RIGHT_PALM), deltaTime));
+ grabCurrentRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM);
+ }
+ if (!grabbingWithRightHand && wasGrabbingWithRightHand) {
+ // Just ending grab, capture velocity
+ grabDeltaVelocity = Controller.getSpatialControlVelocity(RIGHT_PALM);
+ stoppedGrabbingWithRightHand = true;
+ }
+
+ if (grabbingWithLeftHand && !wasGrabbingWithLeftHand) {
+ // Just starting grab, capture starting rotation
+ grabStartRotation = Controller.getSpatialControlRawRotation(LEFT_PALM);
+ }
+
+ if (grabbingWithLeftHand) {
+ grabDelta = Vec3.sum(grabDelta, Vec3.multiply(Controller.getSpatialControlVelocity(LEFT_PALM), deltaTime));
+ grabCurrentRotation = Controller.getSpatialControlRawRotation(LEFT_PALM);
+ }
+ if (!grabbingWithLeftHand && wasGrabbingWithLeftHand) {
+ // Just ending grab, capture velocity
+ grabDeltaVelocity = Controller.getSpatialControlVelocity(LEFT_PALM);
+ stoppedGrabbingWithLeftHand = true;
+ }
+
+ grabbing = grabbingWithRightHand || grabbingWithLeftHand;
+ stoppedGrabbing = stoppedGrabbingWithRightHand || stoppedGrabbingWithLeftHand;
+
+ if (grabbing) {
+
+ // move position
+ var moveFromGrab = getAndResetGrabDelta();
+ if (Vec3.length(moveFromGrab) > EPSILON) {
+ MyAvatar.position = Vec3.sum(MyAvatar.position, moveFromGrab);
+ velocity = { x: 0, y: 0, z: 0};
+ }
+
+ // add some rotation...
+ var deltaRotation = getAndResetGrabRotation();
+ var GRAB_CONTROLLER_TURN_SCALING = 0.5;
+ var euler = Vec3.multiply(Quat.safeEulerAngles(deltaRotation), GRAB_CONTROLLER_TURN_SCALING);
+
+ // Adjust body yaw by yaw from controller
+ var orientation = Quat.multiply(Quat.angleAxis(-euler.y, {x:0, y: 1, z:0}), MyAvatar.orientation);
+ MyAvatar.orientation = orientation;
+
+ // Adjust head pitch from controller
+ MyAvatar.headPitch = MyAvatar.headPitch - euler.x;
+ }
+
+ // add some velocity...
+ if (stoppedGrabbing) {
+ velocity = Vec3.sum(velocity, getAndResetGrabDeltaVelocity());
+ }
+
+ // handle residual velocity
+ if(Vec3.length(velocity) > EPSILON) {
+ MyAvatar.position = Vec3.sum(MyAvatar.position, Vec3.multiply(velocity, deltaTime));
+ // damp velocity
+ velocity = Vec3.multiply(velocity, damping);
+ }
+
+
+ wasGrabbingWithRightHand = grabbingWithRightHand;
+ wasGrabbingWithLeftHand = grabbingWithLeftHand;
+}
+
+// Main update function that handles flying and grabbing behaviort
+function flyWithHydra() {
+ var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
+
+ if (thrustJoystickPosition.x != 0 || thrustJoystickPosition.y != 0) {
+ if (thrustMultiplier < MAX_THRUST_MULTIPLIER) {
+ thrustMultiplier *= 1 + (deltaTime * THRUST_INCREASE_RATE);
+ }
+ var currentOrientation = MyAvatar.orientation;
+
+ var front = Quat.getFront(currentOrientation);
+ var right = Quat.getRight(currentOrientation);
+ var up = Quat.getUp(currentOrientation);
+
+ var thrustFront = Vec3.multiply(front, MyAvatar.scale * THRUST_MAG_HAND_JETS *
+ thrustJoystickPosition.y * thrustMultiplier * deltaTime);
+ MyAvatar.addThrust(thrustFront);
+ var thrustRight = Vec3.multiply(right, MyAvatar.scale * THRUST_MAG_HAND_JETS *
+ thrustJoystickPosition.x * thrustMultiplier * deltaTime);
+ MyAvatar.addThrust(thrustRight);
+ } else {
+ thrustMultiplier = INITIAL_THRUST_MULTPLIER;
+ }
+
+ // View Controller
+ var viewJoystickPosition = Controller.getJoystickPosition(VIEW_CONTROLLER);
+ if (viewJoystickPosition.x != 0 || viewJoystickPosition.y != 0) {
+
+ // change the body yaw based on our x controller
+ var orientation = MyAvatar.orientation;
+ var deltaOrientation = Quat.fromPitchYawRoll(0, (-1 * viewJoystickPosition.x * JOYSTICK_YAW_MAG * deltaTime), 0);
+ MyAvatar.orientation = Quat.multiply(orientation, deltaOrientation);
+
+ // change the headPitch based on our x controller
+ //pitch += viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime;
+ var newPitch = MyAvatar.headPitch + (viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime);
+ MyAvatar.headPitch = newPitch;
+ }
+ handleGrabBehavior();
+}
+
+Script.willSendVisualDataCallback.connect(flyWithHydra);
+Controller.captureJoystick(THRUST_CONTROLLER);
+Controller.captureJoystick(VIEW_CONTROLLER);
+
+// Map keyPress and mouse move events to our callbacks
+function scriptEnding() {
+ // re-enabled the standard application for touch events
+ Controller.releaseJoystick(THRUST_CONTROLLER);
+ Controller.releaseJoystick(VIEW_CONTROLLER);
+}
+Script.scriptEnding.connect(scriptEnding);
+
diff --git a/examples/overlaysExample.js b/examples/overlaysExample.js
new file mode 100644
index 0000000000..b57f82e7e9
--- /dev/null
+++ b/examples/overlaysExample.js
@@ -0,0 +1,279 @@
+//
+// overlaysExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 2/14/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Overlays class
+//
+//
+
+
+// The "Swatches" example of this script will create 9 different image overlays, that use the color feature to
+// display different colors as color swatches. The overlays can be clicked on, to change the "selectedSwatch" variable
+// and update the image used for the overlay so that it appears to have a selected indicator.
+// These are our colors...
+var swatchColors = new Array();
+swatchColors[0] = { red: 255, green: 0, blue: 0};
+swatchColors[1] = { red: 0, green: 255, blue: 0};
+swatchColors[2] = { red: 0, green: 0, blue: 255};
+swatchColors[3] = { red: 255, green: 255, blue: 0};
+swatchColors[4] = { red: 255, green: 0, blue: 255};
+swatchColors[5] = { red: 0, green: 255, blue: 255};
+swatchColors[6] = { red: 128, green: 128, blue: 128};
+swatchColors[7] = { red: 128, green: 0, blue: 0};
+swatchColors[8] = { red: 0, green: 240, blue: 240};
+
+// The location of the placement of these overlays
+var swatchesX = 100;
+var swatchesY = 200;
+
+// These will be our "overlay IDs"
+var swatches = new Array();
+var numberOfSwatches = 9;
+var selectedSwatch = 0;
+
+// create the overlays, position them in a row, set their colors, and for the selected one, use a different source image
+// location so that it displays the "selected" marker
+for (s = 0; s < numberOfSwatches; s++) {
+ var imageFromX = 12 + (s * 27);
+ var imageFromY = 0;
+ if (s == selectedSwatch) {
+ imageFromY = 55;
+ }
+
+ swatches[s] = Overlays.addOverlay("image", {
+ x: 100 + (30 * s),
+ y: 200,
+ width: 31,
+ height: 54,
+ subImage: { x: imageFromX, y: imageFromY, width: 30, height: 54 },
+ imageURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg",
+ color: swatchColors[s],
+ alpha: 1
+ });
+}
+
+// This will create a text overlay that when you click on it, the text will change
+var text = Overlays.addOverlay("text", {
+ x: 200,
+ y: 100,
+ width: 150,
+ height: 50,
+ color: { red: 0, green: 0, blue: 0},
+ textColor: { red: 255, green: 0, blue: 0},
+ topMargin: 4,
+ leftMargin: 4,
+ text: "Here is some text.\nAnd a second line."
+ });
+
+// This will create an image overlay, which starts out as invisible
+var toolA = Overlays.addOverlay("image", {
+ x: 100,
+ y: 100,
+ width: 62,
+ height: 40,
+ subImage: { x: 0, y: 0, width: 62, height: 40 },
+ imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg",
+ color: { red: 255, green: 255, blue: 255},
+ visible: false
+ });
+
+// This will create a couple of image overlays that make a "slider", we will demonstrate how to trap mouse messages to
+// move the slider
+var slider = Overlays.addOverlay("image", {
+ // alternate form of expressing bounds
+ bounds: { x: 100, y: 300, width: 158, height: 35},
+ imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png",
+ color: { red: 255, green: 255, blue: 255},
+ alpha: 1
+ });
+
+// This is the thumb of our slider
+var minThumbX = 130;
+var maxThumbX = minThumbX + 65;
+var thumbX = (minThumbX + maxThumbX) / 2;
+var thumb = Overlays.addOverlay("image", {
+ x: thumbX,
+ y: 309,
+ width: 18,
+ height: 17,
+ imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
+ color: { red: 255, green: 255, blue: 255},
+ alpha: 1
+ });
+
+
+// We will also demonstrate some 3D overlays. We will create a couple of cubes, spheres, and lines
+// our 3D cube that moves around...
+var cubePosition = { x: 2, y: 0, z: 2 };
+var cubeSize = 5;
+var cubeMove = 0.1;
+var minCubeX = 1;
+var maxCubeX = 20;
+
+var cube = Overlays.addOverlay("cube", {
+ position: cubePosition,
+ size: cubeSize,
+ color: { red: 255, green: 0, blue: 0},
+ alpha: 1,
+ solid: false
+ });
+
+var solidCubePosition = { x: 0, y: 5, z: 0 };
+var solidCubeSize = 2;
+var minSolidCubeX = 0;
+var maxSolidCubeX = 10;
+var solidCubeMove = 0.05;
+var solidCube = Overlays.addOverlay("cube", {
+ position: solidCubePosition,
+ size: solidCubeSize,
+ color: { red: 0, green: 255, blue: 0},
+ alpha: 1,
+ solid: true
+ });
+
+var spherePosition = { x: 5, y: 5, z: 5 };
+var sphereSize = 1;
+var minSphereSize = 0.5;
+var maxSphereSize = 10;
+var sphereSizeChange = 0.05;
+
+var sphere = Overlays.addOverlay("sphere", {
+ position: spherePosition,
+ size: sphereSize,
+ color: { red: 0, green: 0, blue: 255},
+ alpha: 1,
+ solid: false
+ });
+
+var line3d = Overlays.addOverlay("line3d", {
+ position: { x: 0, y: 0, z:0 },
+ end: { x: 10, y: 10, z:10 },
+ color: { red: 0, green: 255, blue: 255},
+ alpha: 1,
+ lineWidth: 5
+ });
+
+
+// When our script shuts down, we should clean up all of our overlays
+function scriptEnding() {
+ Overlays.deleteOverlay(toolA);
+ for (s = 0; s < numberOfSwatches; s++) {
+ Overlays.deleteOverlay(swatches[s]);
+ }
+ Overlays.deleteOverlay(thumb);
+ Overlays.deleteOverlay(slider);
+ Overlays.deleteOverlay(text);
+ Overlays.deleteOverlay(cube);
+ Overlays.deleteOverlay(solidCube);
+ Overlays.deleteOverlay(sphere);
+ Overlays.deleteOverlay(line3d);
+}
+Script.scriptEnding.connect(scriptEnding);
+
+
+var toolAVisible = false;
+var count = 0;
+
+// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
+function update() {
+ count++;
+
+ // every second or so, toggle the visibility our our blinking tool
+ if (count % 60 == 0) {
+ if (toolAVisible) {
+ toolAVisible = false;
+ } else {
+ toolAVisible = true;
+ }
+ Overlays.editOverlay(toolA, { visible: toolAVisible } );
+ }
+
+ // move our 3D cube
+ cubePosition.x += cubeMove;
+ cubePosition.z += cubeMove;
+ if (cubePosition.x > maxCubeX || cubePosition.x < minCubeX) {
+ cubeMove = cubeMove * -1;
+ }
+ Overlays.editOverlay(cube, { position: cubePosition } );
+
+ // move our solid 3D cube
+ solidCubePosition.x += solidCubeMove;
+ solidCubePosition.z += solidCubeMove;
+ if (solidCubePosition.x > maxSolidCubeX || solidCubePosition.x < minSolidCubeX) {
+ solidCubeMove = solidCubeMove * -1;
+ }
+ Overlays.editOverlay(solidCube, { position: solidCubePosition } );
+
+ // adjust our 3D sphere
+ sphereSize += sphereSizeChange;
+ if (sphereSize > maxSphereSize || sphereSize < minSphereSize) {
+ sphereSizeChange = sphereSizeChange * -1;
+ }
+ Overlays.editOverlay(sphere, { size: sphereSize, solid: (sphereSizeChange < 0) } );
+
+
+ // update our 3D line to go from origin to our avatar's position
+ Overlays.editOverlay(line3d, { end: MyAvatar.position } );
+}
+Script.willSendVisualDataCallback.connect(update);
+
+
+// The slider is handled in the mouse event callbacks.
+var movingSlider = false;
+var thumbClickOffsetX = 0;
+function mouseMoveEvent(event) {
+ if (movingSlider) {
+ newThumbX = event.x - thumbClickOffsetX;
+ if (newThumbX < minThumbX) {
+ newThumbX = minThumbX;
+ }
+ if (newThumbX > maxThumbX) {
+ newThumbX = maxThumbX;
+ }
+ Overlays.editOverlay(thumb, { x: newThumbX } );
+ }
+}
+
+// we also handle click detection in our mousePressEvent()
+function mousePressEvent(event) {
+ var clickedText = false;
+ var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
+
+ // If the user clicked on the thumb, handle the slider logic
+ if (clickedOverlay == thumb) {
+ movingSlider = true;
+ thumbClickOffsetX = event.x - thumbX;
+
+ } else if (clickedOverlay == text) { // if the user clicked on the text, update text with where you clicked
+
+ Overlays.editOverlay(text, { text: "you clicked here:\n " + event.x + "," + event.y } );
+ clickedText = true;
+
+ } else { // if the user clicked on one of the color swatches, update the selectedSwatch
+
+ for (s = 0; s < numberOfSwatches; s++) {
+ if (clickedOverlay == swatches[s]) {
+ Overlays.editOverlay(swatches[selectedSwatch], { subImage: { y: 0 } } );
+ Overlays.editOverlay(swatches[s], { subImage: { y: 55 } } );
+ selectedSwatch = s;
+ }
+ }
+ }
+ if (!clickedText) { // if you didn't click on the text, then update the text accordningly
+ Overlays.editOverlay(text, { text: "you didn't click here" } );
+ }
+}
+
+function mouseReleaseEvent(event) {
+ if (movingSlider) {
+ movingSlider = false;
+ }
+}
+
+Controller.mouseMoveEvent.connect(mouseMoveEvent);
+Controller.mousePressEvent.connect(mousePressEvent);
+Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
+
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index 6af6ed478d..8e96006828 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -90,7 +90,13 @@ qt5_wrap_ui(QT_UI_HEADERS ${QT_UI_FILES})
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${QT_UI_HEADERS})
if (APPLE)
+
+ # configure CMake to use a custom Info.plist
+ SET_TARGET_PROPERTIES( ${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in )
+
set(MACOSX_BUNDLE_BUNDLE_NAME Interface)
+ set(MACOSX_BUNDLE_GUI_IDENTIFIER io.highfidelity.Interface)
+
# set how the icon shows up in the Info.plist file
SET(MACOSX_BUNDLE_ICON_FILE interface.icns)
diff --git a/interface/resources/meshes/defaultAvatar_body.fbx b/interface/resources/meshes/defaultAvatar/body.fbx
similarity index 100%
rename from interface/resources/meshes/defaultAvatar_body.fbx
rename to interface/resources/meshes/defaultAvatar/body.fbx
diff --git a/interface/resources/meshes/body.jpg b/interface/resources/meshes/defaultAvatar/body.jpg
similarity index 100%
rename from interface/resources/meshes/body.jpg
rename to interface/resources/meshes/defaultAvatar/body.jpg
diff --git a/interface/resources/meshes/defaultAvatar_head.fbx b/interface/resources/meshes/defaultAvatar/head.fbx
similarity index 100%
rename from interface/resources/meshes/defaultAvatar_head.fbx
rename to interface/resources/meshes/defaultAvatar/head.fbx
diff --git a/interface/resources/meshes/tail.jpg b/interface/resources/meshes/defaultAvatar/tail.jpg
similarity index 100%
rename from interface/resources/meshes/tail.jpg
rename to interface/resources/meshes/defaultAvatar/tail.jpg
diff --git a/interface/resources/meshes/visor.png b/interface/resources/meshes/defaultAvatar/visor.png
similarity index 100%
rename from interface/resources/meshes/visor.png
rename to interface/resources/meshes/defaultAvatar/visor.png
diff --git a/interface/resources/meshes/defaultAvatar_body.fst b/interface/resources/meshes/defaultAvatar_body.fst
index 3e8fa3ef45..5874c206db 100644
--- a/interface/resources/meshes/defaultAvatar_body.fst
+++ b/interface/resources/meshes/defaultAvatar_body.fst
@@ -1,3 +1,5 @@
+filename=defaultAvatar/body.fbx
+texdir=defaultAvatar
scale=130
joint = jointRoot = jointRoot
joint = jointLean = jointSpine
diff --git a/interface/resources/meshes/defaultAvatar_head.fst b/interface/resources/meshes/defaultAvatar_head.fst
index 1352652efc..34cf44f0e4 100644
--- a/interface/resources/meshes/defaultAvatar_head.fst
+++ b/interface/resources/meshes/defaultAvatar_head.fst
@@ -1,7 +1,7 @@
# faceshift target mapping file
name= defaultAvatar_head
-filename=../../../Avatars/Jelly/jellyrob_blue.fbx
-texdir=../../../Avatars/Jelly
+filename=defaultAvatar/head.fbx
+texdir=defaultAvatar
scale=80
rx=0
ry=0
diff --git a/interface/resources/shaders/perlin_modulate.frag b/interface/resources/shaders/perlin_modulate.frag
index eea0da3671..8ead57c238 100644
--- a/interface/resources/shaders/perlin_modulate.frag
+++ b/interface/resources/shaders/perlin_modulate.frag
@@ -12,7 +12,7 @@
uniform sampler2D permutationNormalTexture;
// the noise frequency
-const float frequency = 1024.0;
+const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128
// the noise amplitude
const float amplitude = 0.1;
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index c922120e48..6dd6d8b61f 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -101,6 +101,8 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D
const int STATS_PELS_PER_LINE = 20;
+const QString CUSTOM_URL_SCHEME = "hifi:";
+
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (message.size() > 0) {
QString messageWithNewLine = message + "\n";
@@ -289,6 +291,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
checkVersion();
+
+ _overlays.init(_glWidget); // do this before scripts load
+
// do this as late as possible so that all required subsystems are inialized
loadScripts();
@@ -679,6 +684,38 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
}
}
+bool Application::event(QEvent* event) {
+
+ // handle custom URL
+ if (event->type() == QEvent::FileOpen) {
+ QFileOpenEvent* fileEvent = static_cast(event);
+ if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) {
+ QString destination = fileEvent->url().toLocalFile().remove(CUSTOM_URL_SCHEME);
+ QStringList urlParts = destination.split('/', QString::SkipEmptyParts);
+
+ if (urlParts.count() > 1) {
+ // if url has 2 or more parts, the first one is domain name
+ Menu::getInstance()->goToDomain(urlParts[0]);
+
+ // location coordinates
+ Menu::getInstance()->goToDestination(urlParts[1]);
+ if (urlParts.count() > 2) {
+
+ // location orientation
+ Menu::getInstance()->goToOrientation(urlParts[2]);
+ }
+ } else if (urlParts.count() == 1) {
+
+ // location coordinates
+ Menu::getInstance()->goToDestination(urlParts[0]);
+ }
+ }
+
+ return false;
+ }
+ return QApplication::event(event);
+}
+
void Application::keyPressEvent(QKeyEvent* event) {
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
@@ -1875,6 +1912,8 @@ void Application::init() {
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
+
+
}
void Application::closeMirrorView() {
@@ -2146,15 +2185,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) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()");
@@ -2276,7 +2306,6 @@ void Application::update(float deltaTime) {
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...
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
- updateParticles(deltaTime); // Simulate particle cloud movements
updateMetavoxels(deltaTime); // update metavoxels
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
updateDialogs(deltaTime); // update various stats dialogs if present
@@ -2711,16 +2740,18 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
// disable specular lighting for ground and voxels
glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR);
- // Draw Cloud Particles
- if (Menu::getInstance()->isOptionChecked(MenuOption::ParticleCloud)) {
- _cloud.render();
- }
// Draw voxels
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxels...");
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) {
- _voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
+ _voxels.render();
+
+ // double check that our LOD doesn't need to be auto-adjusted
+ // only adjust if our option is set
+ if (Menu::getInstance()->isOptionChecked(MenuOption::AutoAdjustLOD)) {
+ Menu::getInstance()->autoAdjustLOD(_fps);
+ }
}
}
@@ -2811,7 +2842,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
_mouseVoxel.s,
_mouseVoxel.s);
- _sharedVoxelSystem.render(true);
+ _sharedVoxelSystem.render();
glPopMatrix();
}
}
@@ -2851,6 +2882,9 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
// give external parties a change to hook in
emit renderingInWorldInterface();
+
+ // render JS/scriptable overlays
+ _overlays.render3D();
}
}
@@ -2997,6 +3031,8 @@ void Application::displayOverlay() {
_pieMenu.render();
}
+ _overlays.render2D();
+
glPopMatrix();
}
@@ -3998,6 +4034,32 @@ void Application::saveScripts() {
settings->endArray();
}
+void Application::stopAllScripts() {
+ // stops all current running scripts
+ QList 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 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) {
_activeScripts.removeOne(fileNameString);
}
@@ -4042,12 +4104,14 @@ void Application::loadScript(const QString& fileNameString) {
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
// hook our avatar object into this script engine
- scriptEngine->setAvatarData( static_cast(_myAvatar), "MyAvatar");
+ scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features
CameraScriptableObject* cameraScriptable = new CameraScriptableObject(&_myCamera, &_viewFrustum);
scriptEngine->registerGlobalObject("Camera", cameraScriptable);
connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater()));
+ scriptEngine->registerGlobalObject("Overlays", &_overlays);
+
QThread* workerThread = new QThread(this);
// when the worker thread is started, call our engine's run..
diff --git a/interface/src/Application.h b/interface/src/Application.h
index ff6e08758b..a9c20ac00d 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -32,7 +32,6 @@
#include "BandwidthMeter.h"
#include "Camera.h"
-#include "Cloud.h"
#include "DatagramProcessor.h"
#include "Environment.h"
#include "GLCanvas.h"
@@ -71,6 +70,7 @@
#include "FileLogger.h"
#include "ParticleTreeRenderer.h"
#include "ControllerScriptingInterface.h"
+#include "ui/Overlays.h"
class QAction;
@@ -127,7 +127,9 @@ public:
void touchUpdateEvent(QTouchEvent* event);
void wheelEvent(QWheelEvent* event);
-
+
+ bool event(QEvent* event);
+
void makeVoxel(glm::vec3 position,
float scale,
unsigned char red,
@@ -232,6 +234,8 @@ public slots:
void loadDialog();
void toggleLogDialog();
void initAvatarAndViewFrustum();
+ void stopAllScripts();
+ void reloadAllScripts();
private slots:
void timer();
@@ -284,7 +288,6 @@ private:
void updateSixense(float deltaTime);
void updateSerialDevices(float deltaTime);
void updateThreads(float deltaTime);
- void updateParticles(float deltaTime);
void updateMetavoxels(float deltaTime);
void updateCamera(float deltaTime);
void updateDialogs(float deltaTime);
@@ -351,8 +354,6 @@ private:
Stars _stars;
- Cloud _cloud;
-
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
VoxelImporter* _voxelImporter;
@@ -490,6 +491,8 @@ private:
void takeSnapshot();
TouchEvent _lastTouchEvent;
+
+ Overlays _overlays;
};
#endif /* defined(__interface__Application__) */
diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp
index 6eee4453d4..0cf67be2bf 100644
--- a/interface/src/Audio.cpp
+++ b/interface/src/Audio.cpp
@@ -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 int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300;
+
// Mute icon configration
static const int ICON_SIZE = 24;
static const int ICON_LEFT = 0;
@@ -65,7 +67,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
_measuredJitter(0),
_jitterBufferSamples(initialJitterBufferSamples),
_lastInputLoudness(0),
- _averageInputLoudness(0),
+ _noiseGateMeasuredFloor(0),
+ _noiseGateSampleCounter(0),
_noiseGateOpen(false),
_noiseGateEnabled(true),
_noiseGateFramesToClose(0),
@@ -82,6 +85,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
{
// clear the array of locally injected samples
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) {
@@ -351,26 +356,66 @@ void Audio::handleAudioInput() {
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
_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 thisSample = 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_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++) {
thisSample = fabsf(monoAudioSamples[i]);
loudness += thisSample;
// Noise Reduction: Count peaks above the average loudness
- if (thisSample > (_averageInputLoudness * NOISE_GATE_HEIGHT)) {
+ if (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT)) {
samplesOverNoiseGate++;
}
}
_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 (samplesOverNoiseGate > NOISE_GATE_WIDTH) {
_noiseGateOpen = true;
@@ -487,7 +532,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) {
// we don't have any audio data left in the output buffer
// we just starved
- qDebug() << "Audio output just starved.";
+ //qDebug() << "Audio output just starved.";
_ringBuffer.setIsStarved(true);
_numFramesDisplayStarve = 10;
}
@@ -505,7 +550,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
+ (_jitterBufferSamples * 2))) {
// 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 {
// We are either already playing back, or we have enough audio to start playing back.
_ringBuffer.setIsStarved(false);
diff --git a/interface/src/Audio.h b/interface/src/Audio.h
index 88e2731006..34a3daad30 100644
--- a/interface/src/Audio.h
+++ b/interface/src/Audio.h
@@ -45,7 +45,7 @@ public:
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; }
@@ -109,7 +109,9 @@ private:
float _measuredJitter;
int16_t _jitterBufferSamples;
float _lastInputLoudness;
- float _averageInputLoudness;
+ float _noiseGateMeasuredFloor;
+ float* _noiseSampleFrames;
+ int _noiseGateSampleCounter;
bool _noiseGateOpen;
bool _noiseGateEnabled;
int _noiseGateFramesToClose;
diff --git a/interface/src/Cloud.cpp b/interface/src/Cloud.cpp
deleted file mode 100644
index a89ee04810..0000000000
--- a/interface/src/Cloud.cpp
+++ /dev/null
@@ -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
-#include
-#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;
- }
- }
- }
diff --git a/interface/src/Cloud.h b/interface/src/Cloud.h
deleted file mode 100644
index fcf414b62e..0000000000
--- a/interface/src/Cloud.h
+++ /dev/null
@@ -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
diff --git a/interface/src/ControllerScriptingInterface.cpp b/interface/src/ControllerScriptingInterface.cpp
index 8716116877..b3d6170bff 100644
--- a/interface/src/ControllerScriptingInterface.cpp
+++ b/interface/src/ControllerScriptingInterface.cpp
@@ -172,6 +172,21 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlVelocity(int controlInd
return glm::vec3(0); // bad index
}
+glm::quat ControllerScriptingInterface::getSpatialControlRawRotation(int controlIndex) const {
+ int palmIndex = controlIndex / NUMBER_OF_SPATIALCONTROLS_PER_PALM;
+ int controlOfPalm = controlIndex % NUMBER_OF_SPATIALCONTROLS_PER_PALM;
+ const PalmData* palmData = getActivePalm(palmIndex);
+ if (palmData) {
+ switch (controlOfPalm) {
+ case PALM_SPATIALCONTROL:
+ return palmData->getRawRotation();
+ case TIP_SPATIALCONTROL:
+ return palmData->getRawRotation(); // currently the tip doesn't have a unique rotation, use the palm rotation
+ }
+ }
+ return glm::quat(); // bad index
+}
+
glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex) const {
int palmIndex = controlIndex / NUMBER_OF_SPATIALCONTROLS_PER_PALM;
int controlOfPalm = controlIndex % NUMBER_OF_SPATIALCONTROLS_PER_PALM;
diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h
index e9fbd2d6d3..f0a50559f9 100644
--- a/interface/src/ControllerScriptingInterface.h
+++ b/interface/src/ControllerScriptingInterface.h
@@ -58,7 +58,7 @@ public slots:
virtual glm::vec3 getSpatialControlPosition(int controlIndex) const;
virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const;
virtual glm::vec3 getSpatialControlNormal(int controlIndex) const;
-
+ virtual glm::quat getSpatialControlRawRotation(int controlIndex) const;
virtual void captureKeyEvents(const KeyEvent& event);
virtual void releaseKeyEvents(const KeyEvent& event);
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 136f94166b..a8b9cd7bff 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -70,7 +70,8 @@ Menu::Menu() :
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_boundaryLevelAdjust(0),
- _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS)
+ _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS),
+ _lastAdjust(usecTimestampNow())
{
Application *appInstance = Application::getInstance();
@@ -93,6 +94,8 @@ Menu::Menu() :
addDisabledActionAndSeparator(fileMenu, "Scripts");
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");
addDisabledActionAndSeparator(fileMenu, "Voxels");
@@ -161,7 +164,7 @@ Menu::Menu() :
#endif
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);
@@ -238,9 +241,9 @@ Menu::Menu() :
SLOT(setFullscreen(bool)));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true,
appInstance,SLOT(cameraMenuChanged()));
- addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H);
- addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H,false,
- appInstance,SLOT(cameraMenuChanged()));
+ addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true);
+ addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false,
+ appInstance, SLOT(cameraMenuChanged()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0,
false,
@@ -266,14 +269,8 @@ Menu::Menu() :
appInstance->getAvatar(),
SLOT(resetSize()));
- addCheckableActionToQMenuAndActionHash(viewMenu,
- MenuOption::OffAxisProjection,
- 0,
- true);
- addCheckableActionToQMenuAndActionHash(viewMenu,
- MenuOption::TurnWithHead,
- 0,
- true);
+ addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::OffAxisProjection, 0, false);
+ addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MoveWithLean, 0, false);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HeadMouse, 0, false);
@@ -298,7 +295,6 @@ Menu::Menu() :
appInstance->getGlowEffect(),
SLOT(cycleRenderMode()));
- addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ParticleCloud, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, false);
@@ -323,6 +319,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
+ addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutoAdjustLOD);
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
@@ -340,14 +337,14 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionProxies);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionProxies);
- addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, true);
+ addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
MenuOption::FaceshiftTCP,
0,
false,
appInstance->getFaceshift(),
SLOT(setTCPEnabled(bool)));
- addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, true);
+ addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
@@ -357,7 +354,7 @@ Menu::Menu() :
true,
appInstance->getSixenseManager(),
SLOT(setFilter(bool)));
- addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayLeapHands, 0, true);
+ addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::VoxelDrumming, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false);
@@ -890,6 +887,17 @@ void Menu::editPreferences() {
sendFakeEnterEvent();
}
+void Menu::goToDomain(const QString newDomain) {
+ if (NodeList::getInstance()->getDomainHostname() != newDomain) {
+
+ // send a node kill request, indicating to other clients that they should play the "disappeared" effect
+ Application::getInstance()->getAvatar()->sendKillAvatar();
+
+ // give our nodeList the new domain-server hostname
+ NodeList::getInstance()->setDomainHostname(newDomain);
+ }
+}
+
void Menu::goToDomain() {
QString currentDomainHostname = NodeList::getInstance()->getDomainHostname();
@@ -914,17 +922,77 @@ void Menu::goToDomain() {
// the user input a new hostname, use that
newHostname = domainDialog.textValue();
}
-
- // send a node kill request, indicating to other clients that they should play the "disappeared" effect
- Application::getInstance()->getAvatar()->sendKillAvatar();
-
- // give our nodeList the new domain-server hostname
- NodeList::getInstance()->setDomainHostname(domainDialog.textValue());
+
+ goToDomain(newHostname);
}
sendFakeEnterEvent();
}
+void Menu::goToOrientation(QString orientation) {
+
+ if (orientation.isEmpty()) {
+ return;
+ }
+
+ QStringList orientationItems = orientation.split(QRegExp("_|,"), QString::SkipEmptyParts);
+
+ const int NUMBER_OF_ORIENTATION_ITEMS = 4;
+ const int W_ITEM = 0;
+ const int X_ITEM = 1;
+ const int Y_ITEM = 2;
+ const int Z_ITEM = 3;
+
+ if (orientationItems.size() == NUMBER_OF_ORIENTATION_ITEMS) {
+
+ double w = replaceLastOccurrence('-', '.', orientationItems[W_ITEM].trimmed()).toDouble();
+ double x = replaceLastOccurrence('-', '.', orientationItems[X_ITEM].trimmed()).toDouble();
+ double y = replaceLastOccurrence('-', '.', orientationItems[Y_ITEM].trimmed()).toDouble();
+ double z = replaceLastOccurrence('-', '.', orientationItems[Z_ITEM].trimmed()).toDouble();
+
+ glm::quat newAvatarOrientation(w, x, y, z);
+
+ MyAvatar* myAvatar = Application::getInstance()->getAvatar();
+ glm::quat avatarOrientation = myAvatar->getOrientation();
+ if (newAvatarOrientation != avatarOrientation) {
+ myAvatar->setOrientation(newAvatarOrientation);
+ }
+ }
+}
+
+bool Menu::goToDestination(QString destination) {
+
+ QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
+
+ const int NUMBER_OF_COORDINATE_ITEMS = 3;
+ const int X_ITEM = 0;
+ const int Y_ITEM = 1;
+ const int Z_ITEM = 2;
+ if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
+
+ double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
+ double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
+ double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
+
+ glm::vec3 newAvatarPos(x, y, z);
+
+ MyAvatar* myAvatar = Application::getInstance()->getAvatar();
+ glm::vec3 avatarPos = myAvatar->getPosition();
+ if (newAvatarPos != avatarPos) {
+ // send a node kill request, indicating to other clients that they should play the "disappeared" effect
+ MyAvatar::sendKillAvatar();
+
+ qDebug("Going To Location: %f, %f, %f...", x, y, z);
+ myAvatar->setPosition(newAvatarPos);
+ }
+
+ return true;
+ }
+
+ // no coordinates were parsed
+ return false;
+}
+
void Menu::goTo() {
QInputDialog gotoDialog(Application::getInstance()->getWindow());
@@ -940,31 +1008,8 @@ void Menu::goTo() {
destination = gotoDialog.textValue();
- QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
-
- const int NUMBER_OF_COORDINATE_ITEMS = 3;
- const int X_ITEM = 0;
- const int Y_ITEM = 1;
- const int Z_ITEM = 2;
- if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
-
- double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
- double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
- double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
-
- glm::vec3 newAvatarPos(x, y, z);
-
- MyAvatar* myAvatar = Application::getInstance()->getAvatar();
- glm::vec3 avatarPos = myAvatar->getPosition();
- if (newAvatarPos != avatarPos) {
- // send a node kill request, indicating to other clients that they should play the "disappeared" effect
- MyAvatar::sendKillAvatar();
-
- qDebug("Going To Location: %f, %f, %f...", x, y, z);
- myAvatar->setPosition(newAvatarPos);
- }
-
- } else {
+ // go to coordinate destination or to Username
+ if (!goToDestination(destination)) {
// there's a username entered by the user, make a request to the data-server
DataServerClient::getValuesForKeysAndUserString(
QStringList()
@@ -995,29 +1040,7 @@ void Menu::goToLocation() {
int dialogReturn = coordinateDialog.exec();
if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) {
- QByteArray newCoordinates;
-
- QString delimiterPattern(",");
- QStringList coordinateItems = coordinateDialog.textValue().split(delimiterPattern);
-
- const int NUMBER_OF_COORDINATE_ITEMS = 3;
- const int X_ITEM = 0;
- const int Y_ITEM = 1;
- const int Z_ITEM = 2;
- if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
- double x = coordinateItems[X_ITEM].toDouble();
- double y = coordinateItems[Y_ITEM].toDouble();
- double z = coordinateItems[Z_ITEM].toDouble();
- glm::vec3 newAvatarPos(x, y, z);
-
- if (newAvatarPos != avatarPos) {
- // send a node kill request, indicating to other clients that they should play the "disappeared" effect
- MyAvatar::sendKillAvatar();
-
- qDebug("Going To Location: %f, %f, %f...", x, y, z);
- myAvatar->setPosition(newAvatarPos);
- }
- }
+ goToDestination(coordinateDialog.textValue());
}
sendFakeEnterEvent();
@@ -1097,14 +1120,38 @@ void Menu::voxelStatsDetailsClosed() {
}
}
+void Menu::autoAdjustLOD(float currentFPS) {
+ bool changed = false;
+ quint64 now = usecTimestampNow();
+ quint64 elapsed = now - _lastAdjust;
+
+ if (elapsed > ADJUST_LOD_DOWN_DELAY && currentFPS < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
+ _voxelSizeScale *= ADJUST_LOD_DOWN_BY;
+ changed = true;
+ _lastAdjust = now;
+ qDebug() << "adjusting LOD down... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
+ }
+
+ if (elapsed > ADJUST_LOD_UP_DELAY && currentFPS > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
+ _voxelSizeScale *= ADJUST_LOD_UP_BY;
+ changed = true;
+ _lastAdjust = now;
+ qDebug() << "adjusting LOD up... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
+ }
+
+ if (changed) {
+ if (_lodToolsDialog) {
+ _lodToolsDialog->reloadSliders();
+ }
+ }
+}
+
void Menu::setVoxelSizeScale(float sizeScale) {
_voxelSizeScale = sizeScale;
- Application::getInstance()->getVoxels()->redrawInViewVoxels();
}
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
_boundaryLevelAdjust = boundaryLevelAdjust;
- Application::getInstance()->getVoxels()->redrawInViewVoxels();
}
void Menu::lodTools() {
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 3df986d29a..c0db7105e4 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -16,6 +16,18 @@
#include
+const float ADJUST_LOD_DOWN_FPS = 40.0;
+const float ADJUST_LOD_UP_FPS = 55.0;
+
+const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5;
+const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
+
+const float ADJUST_LOD_DOWN_BY = 0.9f;
+const float ADJUST_LOD_UP_BY = 1.1f;
+
+const float ADJUST_LOD_MIN_SIZE_SCALE = TREE_SCALE * 1.0f;
+const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
+
enum FrustumDrawMode {
FRUSTUM_DRAW_MODE_ALL,
FRUSTUM_DRAW_MODE_VECTORS,
@@ -68,6 +80,7 @@ public:
void handleViewFrustumOffsetKeyModifier(int key);
// User Tweakable LOD Items
+ void autoAdjustLOD(float currentFPS);
void setVoxelSizeScale(float sizeScale);
float getVoxelSizeScale() const { return _voxelSizeScale; }
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
@@ -84,6 +97,9 @@ public:
const char* member = NULL,
QAction::MenuRole role = QAction::NoRole);
virtual void removeAction(QMenu* menu, const QString& actionName);
+ bool goToDestination(QString destination);
+ void goToOrientation(QString orientation);
+ void goToDomain(const QString newDomain);
public slots:
void bandwidthDetails();
@@ -154,6 +170,7 @@ private:
int _maxVoxelPacketsPerSecond;
QMenu* _activeScriptsMenu;
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
+ quint64 _lastAdjust;
};
namespace MenuOption {
@@ -161,6 +178,7 @@ namespace MenuOption {
const QString AmbientOcclusion = "Ambient Occlusion";
const QString Avatars = "Avatars";
const QString Atmosphere = "Atmosphere";
+ const QString AutoAdjustLOD = "Automatically Adjust LOD";
const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats";
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";
@@ -182,7 +200,7 @@ namespace MenuOption {
const QString DisableDeltaSending = "Disable Delta Sending";
const QString DisableLowRes = "Disable Lower Resolution While Moving";
const QString DisplayFrustum = "Display Frustum";
- const QString DisplayLeapHands = "Display Leap Hands";
+ const QString DisplayHands = "Display Hands";
const QString DisplayHandTargets = "Display Hand Targets";
const QString FilterSixense = "Smooth Sixense Movement";
const QString DontRenderVoxels = "Don't call _voxels.render()";
@@ -222,7 +240,6 @@ namespace MenuOption {
const QString KillLocalVoxels = "Kill Local Voxels";
const QString GoHome = "Go Home";
const QString Gravity = "Use Gravity";
- const QString ParticleCloud = "Particle Cloud";
const QString LodTools = "LOD Tools";
const QString Log = "Log";
const QString Login = "Login";
@@ -243,8 +260,10 @@ namespace MenuOption {
const QString PasteVoxels = "Paste";
const QString PasteToVoxel = "Paste to Voxel...";
const QString PipelineWarnings = "Show Render Pipeline Warnings";
+ const QString PlaySlaps = "Play Slaps";
const QString Preferences = "Preferences...";
const QString RandomizeVoxelColors = "Randomize Voxel TRUE Colors";
+ const QString ReloadAllScripts = "Reload All Scripts";
const QString RenderSkeletonCollisionProxies = "Skeleton Collision Proxies";
const QString RenderHeadCollisionProxies = "Head Collision Proxies";
const QString ResetAvatarSize = "Reset Avatar Size";
@@ -255,11 +274,10 @@ namespace MenuOption {
const QString SettingsExport = "Export Settings";
const QString ShowAllLocalVoxels = "Show All Local Voxels";
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 Stars = "Stars";
const QString Stats = "Stats";
+ const QString StopAllScripts = "Stop All Scripts";
const QString TestPing = "Test Ping";
const QString TreeStats = "Calculate Tree Stats";
const QString TransmitterDrive = "Transmitter Drive";
@@ -270,6 +288,7 @@ namespace MenuOption {
const QString VoxelAddMode = "Add Voxel Mode";
const QString VoxelColorMode = "Color Voxel Mode";
const QString VoxelDeleteMode = "Delete Voxel Mode";
+ const QString VoxelDrumming = "Voxel Drumming";
const QString VoxelGetColorMode = "Get Color Mode";
const QString VoxelMode = "Cycle Voxel Mode";
const QString VoxelPaintColor = "Voxel Paint Color";
diff --git a/interface/src/Util.h b/interface/src/Util.h
index 09d1fa0484..0c762ccd79 100644
--- a/interface/src/Util.h
+++ b/interface/src/Util.h
@@ -19,15 +19,6 @@
#include
#include
-// the standard sans serif font family
-#define SANS_FONT_FAMILY "Helvetica"
-
-// the standard mono font family
-#define MONO_FONT_FAMILY "Courier"
-
-// the Inconsolata font family
-#define INCONSOLATA_FONT_FAMILY "Inconsolata"
-
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up);
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos);
diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp
index 1ffc6c3e5e..a3352f36e7 100644
--- a/interface/src/VoxelSystem.cpp
+++ b/interface/src/VoxelSystem.cpp
@@ -57,9 +57,12 @@ GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 };
VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
: NodeData(),
- _treeScale(treeScale),
- _maxVoxels(maxVoxels),
- _initialized(false) {
+ _treeScale(treeScale),
+ _maxVoxels(maxVoxels),
+ _initialized(false),
+ _writeArraysLock(QReadWriteLock::Recursive),
+ _readArraysLock(QReadWriteLock::Recursive)
+ {
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
_writeRenderFullVBO = true;
@@ -99,6 +102,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
_culledOnce = false;
_inhideOutOfView = false;
+
+ _lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
+ _lastKnownBoundaryLevelAdjust = 0;
}
void VoxelSystem::elementDeleted(OctreeElement* element) {
@@ -121,6 +127,7 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) {
void VoxelSystem::elementUpdated(OctreeElement* element) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
+
// If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail..
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
return;
@@ -249,6 +256,9 @@ VoxelSystem::~VoxelSystem() {
delete _tree;
}
+
+// This is called by the main application thread on both the initialization of the application and when
+// the preferences dialog box is called/saved
void VoxelSystem::setMaxVoxels(int maxVoxels) {
if (maxVoxels == _maxVoxels) {
return;
@@ -267,6 +277,8 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) {
}
}
+// This is called by the main application thread on both the initialization of the application and when
+// the use voxel shader menu item is chosen
void VoxelSystem::setUseVoxelShader(bool useVoxelShader) {
if (_useVoxelShader == useVoxelShader) {
return;
@@ -330,7 +342,7 @@ void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) {
void VoxelSystem::cleanupVoxelMemory() {
if (_initialized) {
- _bufferWriteLock.lock();
+ _readArraysLock.lockForWrite();
_initialized = false; // no longer initialized
if (_useVoxelShader) {
// these are used when in VoxelShader mode.
@@ -368,7 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() {
delete[] _writeVoxelDirtyArray;
delete[] _readVoxelDirtyArray;
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
- _bufferWriteLock.unlock();
+ _readArraysLock.unlock();
}
}
@@ -401,7 +413,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice
}
void VoxelSystem::initVoxelMemory() {
- _bufferWriteLock.lock();
+ _readArraysLock.lockForWrite();
+ _writeArraysLock.lockForWrite();
_memoryUsageRAM = 0;
_memoryUsageVBO = 0; // our VBO allocations as we know them
@@ -516,7 +529,8 @@ void VoxelSystem::initVoxelMemory() {
_initialized = true;
- _bufferWriteLock.unlock();
+ _writeArraysLock.unlock();
+ _readArraysLock.unlock();
}
void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const {
@@ -646,7 +660,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
}
_inSetupNewVoxelsForDrawing = true;
-
+
bool didWriteFullVBO = _writeRenderFullVBO;
if (_tree->isDirty()) {
static char buffer[64] = { 0 };
@@ -673,7 +687,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
}
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
- _bufferWriteLock.lock();
+ _readArraysLock.lockForWrite();
if (_voxelsUpdated) {
_voxelsDirty=true;
@@ -682,7 +696,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
copyWrittenDataToReadArrays(didWriteFullVBO);
- _bufferWriteLock.unlock();
+ _readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
@@ -713,8 +727,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
- "setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );
- _bufferWriteLock.lock();
+ "setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" );
+ _readArraysLock.lockForWrite();
}
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
@@ -725,7 +739,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
// after...
_voxelsUpdated = 0;
- _bufferWriteLock.unlock();
+ _readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
@@ -733,8 +747,73 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
}
-void VoxelSystem::checkForCulling() {
+
+class recreateVoxelGeometryInViewArgs {
+public:
+ VoxelSystem* thisVoxelSystem;
+ ViewFrustum thisViewFrustum;
+ unsigned long nodesScanned;
+ float voxelSizeScale;
+ int boundaryLevelAdjust;
+
+ recreateVoxelGeometryInViewArgs(VoxelSystem* voxelSystem) :
+ thisVoxelSystem(voxelSystem),
+ thisViewFrustum(*voxelSystem->getViewFrustum()),
+ nodesScanned(0),
+ voxelSizeScale(Menu::getInstance()->getVoxelSizeScale()),
+ boundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust())
+ {
+ }
+};
+
+// The goal of this operation is to remove any old references to old geometry, and if the voxel
+// should be visible, create new geometry for it.
+bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData) {
+ VoxelTreeElement* voxel = (VoxelTreeElement*)element;
+ recreateVoxelGeometryInViewArgs* args = (recreateVoxelGeometryInViewArgs*)extraData;
+
+ args->nodesScanned++;
+
+ // reset the old geometry...
+ // note: this doesn't "mark the voxel as changed", so it only releases the old buffer index thereby forgetting the
+ // old geometry
+ voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
+
+ bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust);
+ bool inView = voxel->isInView(args->thisViewFrustum);
+ voxel->setShouldRender(inView && shouldRender);
+ if (shouldRender && inView) {
+ // recreate the geometry
+ args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW
+ }
+
+ return true; // keep recursing!
+}
+
+
+// TODO: does cleanupRemovedVoxels() ever get called?
+// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots???
+void VoxelSystem::recreateVoxelGeometryInView() {
+
+ qDebug() << "recreateVoxelGeometryInView()...";
+
+ recreateVoxelGeometryInViewArgs args(this);
+ _writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done
+ _tree->lockForRead(); // don't let anyone change our tree structure until we're run
+
+ // reset our write arrays bookkeeping to think we've got no voxels in it
+ clearFreeBufferIndexes();
+
+ // do we need to reset out _writeVoxelDirtyArray arrays??
+ memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool));
+
+ _tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args);
+ _tree->unlock();
+ _writeArraysLock.unlock();
+}
+
+void VoxelSystem::checkForCulling() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
quint64 start = usecTimestampNow();
@@ -762,7 +841,20 @@ void VoxelSystem::checkForCulling() {
_hasRecentlyChanged = false;
}
- hideOutOfView(forceFullFrustum);
+ // This would be a good place to do a special processing pass, for example, switching the LOD of the scene
+ bool fullRedraw = (_lastKnownVoxelSizeScale != Menu::getInstance()->getVoxelSizeScale() ||
+ _lastKnownBoundaryLevelAdjust != Menu::getInstance()->getBoundaryLevelAdjust());
+
+ // track that these values
+ _lastKnownVoxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
+ _lastKnownBoundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
+
+ if (fullRedraw) {
+ // this will remove all old geometry and recreate the correct geometry for all in view voxels
+ recreateVoxelGeometryInView();
+ } else {
+ hideOutOfView(forceFullFrustum);
+ }
if (forceFullFrustum) {
quint64 endViewCulling = usecTimestampNow();
@@ -880,12 +972,26 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"copyWrittenDataToReadArrays()");
- if (_voxelsDirty && _voxelsUpdated) {
- if (fullVBOs) {
- copyWrittenDataToReadArraysFullVBOs();
+ // attempt to get the writeArraysLock for reading and the readArraysLock for writing
+ // so we can copy from the write to the read... if we fail, that's ok, we'll get it the next
+ // time around, the only side effect is the VBOs won't be updated this frame
+ const int WAIT_FOR_LOCK_IN_MS = 5;
+ if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) {
+ if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
+ if (_voxelsDirty && _voxelsUpdated) {
+ if (fullVBOs) {
+ copyWrittenDataToReadArraysFullVBOs();
+ } else {
+ copyWrittenDataToReadArraysPartialVBOs();
+ }
+ }
+ _writeArraysLock.unlock();
} else {
- copyWrittenDataToReadArraysPartialVBOs();
+ qDebug() << "couldn't get _writeArraysLock.LockForRead()...";
}
+ _readArraysLock.unlock();
+ } else {
+ qDebug() << "couldn't get _readArraysLock.LockForWrite()...";
}
}
@@ -1141,17 +1247,27 @@ void VoxelSystem::updateVBOs() {
// would like to include _callsToTreesToArrays
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
if (_voxelsDirty) {
- if (_readRenderFullVBO) {
- updateFullVBOs();
+
+ // attempt to lock the read arrays, to for copying from them to the actual GPU VBOs.
+ // if we fail to get the lock, that's ok, our VBOs will update on the next frame...
+ const int WAIT_FOR_LOCK_IN_MS = 5;
+ if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
+ if (_readRenderFullVBO) {
+ updateFullVBOs();
+ } else {
+ updatePartialVBOs();
+ }
+ _voxelsDirty = false;
+ _readRenderFullVBO = false;
+ _readArraysLock.unlock();
} else {
- updatePartialVBOs();
+ qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()";
}
- _voxelsDirty = false;
- _readRenderFullVBO = false;
}
_callsToTreesToArrays = 0; // clear it
}
+// this should only be called on the main application thread during render
void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarning, "updateVBOSegment()");
@@ -1197,7 +1313,8 @@ void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex seg
}
}
-void VoxelSystem::render(bool texture) {
+void VoxelSystem::render() {
+ bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures);
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "render()");
@@ -1404,11 +1521,7 @@ void VoxelSystem::killLocalVoxels() {
setupNewVoxelsForDrawing();
}
-void VoxelSystem::redrawInViewVoxels() {
- hideOutOfView(true);
-}
-
-
+// only called on main thread
bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) {
_nodeCount++;
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
@@ -1416,12 +1529,15 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void
return true;
}
+// only called on main thread, and also always followed by a call to cleanupVoxelMemory()
+// you shouldn't be calling this on any other thread or without also cleaning up voxel memory
void VoxelSystem::clearAllNodesBufferIndex() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::clearAllNodesBufferIndex()");
_nodeCount = 0;
_tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read
_tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation);
+ clearFreeBufferIndexes(); // this should be called too
_tree->unlock();
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
qDebug("clearing buffer index of %d nodes", _nodeCount);
diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h
index d1404668bf..121a7f86c4 100644
--- a/interface/src/VoxelSystem.h
+++ b/interface/src/VoxelSystem.h
@@ -53,7 +53,7 @@ public:
virtual void init();
void simulate(float deltaTime) { }
- void render(bool texture);
+ void render();
void changeTree(VoxelTree* newTree);
VoxelTree* getTree() const { return _tree; }
@@ -79,7 +79,6 @@ public:
unsigned long getVoxelMemoryUsageGPU();
void killLocalVoxels();
- void redrawInViewVoxels();
virtual void removeOutOfView();
virtual void hideOutOfView(bool forceFullFrustum = false);
@@ -151,6 +150,7 @@ protected:
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
void checkForCulling();
+ void recreateVoxelGeometryInView();
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
@@ -194,6 +194,7 @@ private:
static bool showAllSubTreeOperation(OctreeElement* element, void* extraData);
static bool showAllLocalVoxelsOperation(OctreeElement* element, void* extraData);
static bool getVoxelEnclosingOperation(OctreeElement* element, void* extraData);
+ static bool recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData);
int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw);
int forceRemoveNodeFromArrays(VoxelTreeElement* node);
@@ -211,6 +212,11 @@ private:
GLfloat* _readVerticesArray;
GLubyte* _readColorsArray;
+
+ QReadWriteLock _writeArraysLock;
+ QReadWriteLock _readArraysLock;
+
+
GLfloat* _writeVerticesArray;
GLubyte* _writeColorsArray;
bool* _writeVoxelDirtyArray;
@@ -253,9 +259,6 @@ private:
GLuint _vboIndicesFront;
GLuint _vboIndicesBack;
- QMutex _bufferWriteLock;
- QMutex _treeLock;
-
ViewFrustum _lastKnownViewFrustum;
ViewFrustum _lastStableViewFrustum;
ViewFrustum* _viewFrustum;
@@ -299,6 +302,9 @@ private:
bool _useFastVoxelPipeline;
bool _inhideOutOfView;
+
+ float _lastKnownVoxelSizeScale;
+ int _lastKnownBoundaryLevelAdjust;
};
#endif
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index bc7f56f534..353a8aea5f 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -278,11 +278,14 @@ bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penet
bool didPenetrate = false;
glm::vec3 skeletonPenetration;
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)) {
collisionInfo._model = &_skeletonModel;
collisions.push_back(collisionInfo);
didPenetrate = true;
}
+ */
if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
collisionInfo._model = &(_head.getFaceModel());
collisions.push_back(collisionInfo);
@@ -347,13 +350,13 @@ bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, floa
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
- const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx");
+ const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
}
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
- const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx");
+ const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst");
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
}
@@ -447,15 +450,36 @@ float Avatar::getHeight() const {
return extents.maximum.y - extents.minimum.y;
}
-bool Avatar::poke(ModelCollisionInfo& collision) {
- // ATM poke() can only affect the Skeleton (not the head)
+bool Avatar::collisionWouldMoveAvatar(ModelCollisionInfo& collision) const {
+ // ATM only the Skeleton is pokeable
// TODO: make poke affect head
+ if (!collision._model) {
+ return false;
+ }
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;
}
+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 {
return -_skeletonModel.getBindExtents().minimum.y;
}
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 8290115240..2fc26a36b5 100755
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -128,9 +128,11 @@ public:
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
- /// \return true if the collision affects the Avatar models
- bool poke(ModelCollisionInfo& collision);
+ void applyCollision(ModelCollisionInfo& collision);
public slots:
void updateCollisionFlags();
diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp
index 076ad074f2..2a1593b776 100644
--- a/interface/src/avatar/FaceModel.cpp
+++ b/interface/src/avatar/FaceModel.cpp
@@ -20,6 +20,7 @@ FaceModel::FaceModel(Head* owningHead) :
void FaceModel::simulate(float deltaTime) {
if (!isActive()) {
+ Model::simulate(deltaTime);
return;
}
Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar);
@@ -55,7 +56,7 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX
glm::mat3 axes = glm::mat3_cast(_rotation);
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.transform * glm::translate(state.translation) *
joint.preTransform * glm::mat4_cast(joint.preRotation)));
- state.rotation = glm::angleAxis(-_owningHead->getRoll(), glm::normalize(inverse * axes[2])) *
+ state.rotation = glm::angleAxis(-_owningHead->getTweakedRoll(), glm::normalize(inverse * axes[2])) *
glm::angleAxis(_owningHead->getTweakedYaw(), glm::normalize(inverse * axes[1])) *
glm::angleAxis(-_owningHead->getTweakedPitch(), glm::normalize(inverse * axes[0])) * joint.rotation;
}
diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp
index 939ebaeae9..37d7713276 100644
--- a/interface/src/avatar/Hand.cpp
+++ b/interface/src/avatar/Hand.cpp
@@ -32,12 +32,7 @@ Hand::Hand(Avatar* owningAvatar) :
_ballColor(0.0, 0.0, 0.4),
_collisionCenter(0,0,0),
_collisionAge(0),
- _collisionDuration(0),
- _pitchUpdate(0),
- _grabDelta(0, 0, 0),
- _grabDeltaVelocity(0, 0, 0),
- _grabStartRotation(0, 0, 0, 1),
- _grabCurrentRotation(0, 0, 0, 1)
+ _collisionDuration(0)
{
}
@@ -54,28 +49,6 @@ void Hand::init() {
void Hand::reset() {
}
-glm::vec3 Hand::getAndResetGrabDelta() {
- const float HAND_GRAB_SCALE_DISTANCE = 2.f;
- glm::vec3 delta = _grabDelta * _owningAvatar->getScale() * HAND_GRAB_SCALE_DISTANCE;
- _grabDelta = glm::vec3(0,0,0);
- glm::quat avatarRotation = _owningAvatar->getOrientation();
- return avatarRotation * -delta;
-}
-
-glm::vec3 Hand::getAndResetGrabDeltaVelocity() {
- const float HAND_GRAB_SCALE_VELOCITY = 5.f;
- glm::vec3 delta = _grabDeltaVelocity * _owningAvatar->getScale() * HAND_GRAB_SCALE_VELOCITY;
- _grabDeltaVelocity = glm::vec3(0,0,0);
- glm::quat avatarRotation = _owningAvatar->getOrientation();
- return avatarRotation * -delta;
-
-}
-glm::quat Hand::getAndResetGrabRotation() {
- glm::quat diff = _grabCurrentRotation * glm::inverse(_grabStartRotation);
- _grabStartRotation = _grabCurrentRotation;
- return diff;
-}
-
void Hand::simulate(float deltaTime, bool isMine) {
if (_collisionAge > 0.f) {
@@ -84,7 +57,6 @@ void Hand::simulate(float deltaTime, bool isMine) {
if (isMine) {
_buckyBalls.simulate(deltaTime);
- updateCollisions();
}
calculateGeometry();
@@ -99,19 +71,6 @@ void Hand::simulate(float deltaTime, bool isMine) {
_buckyBalls.grab(palm, fingerTipPosition, _owningAvatar->getOrientation(), deltaTime);
- if (palm.getControllerButtons() & BUTTON_4) {
- _grabDelta += palm.getRawVelocity() * deltaTime;
- _grabCurrentRotation = palm.getRawRotation();
- }
- if ((palm.getLastControllerButtons() & BUTTON_4) && !(palm.getControllerButtons() & BUTTON_4)) {
- // Just ending grab, capture velocity
- _grabDeltaVelocity = palm.getRawVelocity();
- }
- if (!(palm.getLastControllerButtons() & BUTTON_4) && (palm.getControllerButtons() & BUTTON_4)) {
- // Just starting grab, capture starting rotation
- _grabStartRotation = palm.getRawRotation();
- }
-
if (palm.getControllerButtons() & BUTTON_1) {
if (glm::length(fingerTipPosition - _lastFingerAddVoxel) > (FINGERTIP_VOXEL_SIZE / 2.f)) {
QColor paintColor = Menu::getInstance()->getActionForOption(MenuOption::VoxelPaintColor)->data().value();
@@ -166,91 +125,105 @@ void Hand::simulate(float deltaTime, bool isMine) {
}
}
-void Hand::updateCollisions() {
- // use position to obtain the left and right palm indices
- int leftPalmIndex, rightPalmIndex;
- getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
-
- ModelCollisionList collisions;
- // check for collisions
+void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
+ if (!avatar || avatar == _owningAvatar) {
+ // don't collide with our own hands (that is done elsewhere)
+ return;
+ }
+ float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
for (size_t i = 0; i < getNumPalms(); i++) {
PalmData& palm = getPalms()[i];
if (!palm.isActive()) {
continue;
}
- float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
glm::vec3 totalPenetration;
-
- if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) {
- // check other avatars
- foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
- Avatar* avatar = static_cast(avatarPointer.data());
- if (avatar == _owningAvatar) {
- // don't collid with our own hands
+ ModelCollisionList collisions;
+ if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
+ // Check for palm collisions
+ glm::vec3 myPalmPosition = palm.getPosition();
+ float palmCollisionDistance = 0.1f;
+ bool wasColliding = palm.getIsCollidingWithPalm();
+ 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;
}
- if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
- // Check for palm collisions
- glm::vec3 myPalmPosition = palm.getPosition();
- float palmCollisionDistance = 0.1f;
- bool wasColliding = palm.getIsCollidingWithPalm();
- 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;
- }
- glm::vec3 otherPalmPosition = otherPalm.getPosition();
- if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
- palm.setIsCollidingWithPalm(true);
- if (!wasColliding) {
- 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);
+ glm::vec3 otherPalmPosition = otherPalm.getPosition();
+ if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
+ palm.setIsCollidingWithPalm(true);
+ if (!wasColliding) {
+ 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 (Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
- // and the current avatar (ignoring everything below the parent of the parent of the last free joint)
- 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 (size_t j = 0; j < collisions.size(); ++j) {
- totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
+ if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) {
+ for (int j = 0; j < collisions.size(); ++j) {
+ if (isMyHand) {
+ if (!avatar->collisionWouldMoveAvatar(collisions[j])) {
+ // we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
+ // not expected to respond to the collision (hand hit unmovable part of their Avatar)
+ totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
+ }
+ } else {
+ // when !isMyHand then avatar is MyAvatar and we apply the collision
+ // 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]);
}
}
}
-
- // un-penetrate
- palm.addToPosition(-totalPenetration);
+ if (isMyHand) {
+ // resolve penetration
+ 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();
+ 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);
}
}
@@ -344,7 +317,7 @@ void Hand::render(bool isMine) {
}
}
- if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayLeapHands)) {
+ if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) {
renderLeapHands(isMine);
}
diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h
index 3c8ec2d562..5a423630b4 100755
--- a/interface/src/avatar/Hand.h
+++ b/interface/src/avatar/Hand.h
@@ -58,14 +58,8 @@ public:
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
- // Pitch from controller input to view
- const float getPitchUpdate() const { return _pitchUpdate; }
- void setPitchUpdate(float pitchUpdate) { _pitchUpdate = pitchUpdate; }
-
- // Get the drag distance to move
- glm::vec3 getAndResetGrabDelta();
- glm::vec3 getAndResetGrabDeltaVelocity();
- glm::quat getAndResetGrabRotation();
+ void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
+ void collideAgainstOurself();
private:
// disallow copies of the Hand, copy of owning Avatar is disallowed too
@@ -96,17 +90,9 @@ private:
void renderLeapHands(bool isMine);
void renderLeapFingerTrails();
- void updateCollisions();
void calculateGeometry();
void handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime);
-
- float _pitchUpdate;
-
- glm::vec3 _grabDelta;
- glm::vec3 _grabDeltaVelocity;
- glm::quat _grabStartRotation;
- glm::quat _grabCurrentRotation;
};
#endif
diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp
index e5d4724bb5..bb88530aa7 100644
--- a/interface/src/avatar/Head.cpp
+++ b/interface/src/avatar/Head.cpp
@@ -219,6 +219,34 @@ float Head::getTweakedRoll() const {
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(_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) {
Application::getInstance()->getGlowEffect()->begin();
diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h
index 19f9efd8e6..eae8223903 100644
--- a/interface/src/avatar/Head.h
+++ b/interface/src/avatar/Head.h
@@ -79,6 +79,8 @@ public:
float getTweakedPitch() const;
float getTweakedYaw() const;
float getTweakedRoll() const;
+
+ void applyCollision(ModelCollisionInfo& collisionInfo);
private:
// disallow copies of the Head, copy of owning Avatar is disallowed too
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 3b1fa90959..26b67761f5 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -111,23 +111,7 @@ void MyAvatar::updateTransmitter(float deltaTime) {
void MyAvatar::update(float deltaTime) {
updateTransmitter(deltaTime);
- // TODO: resurrect touch interactions between avatars
- //// 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));
+ updateFromGyros(deltaTime);
// Update head mouse from faceshift if active
Faceshift* faceshift = Application::getInstance()->getFaceshift();
@@ -224,8 +208,6 @@ void MyAvatar::simulate(float deltaTime) {
updateCollisionWithVoxels(deltaTime, radius);
}
if (_collisionFlags & COLLISION_GROUP_AVATARS) {
- // Note, hand-vs-avatar collisions are done elsewhere
- // This is where we avatar-vs-avatar bounding capsule
updateCollisionWithAvatars(deltaTime);
}
}
@@ -327,24 +309,10 @@ void MyAvatar::simulate(float deltaTime) {
updateChatCircle(deltaTime);
- // Get any position, velocity, or rotation update from Grab Drag controller
- glm::vec3 moveFromGrab = _hand.getAndResetGrabDelta();
- if (glm::length(moveFromGrab) > EPSILON) {
- _position += moveFromGrab;
- _velocity = glm::vec3(0, 0, 0);
- }
- _velocity += _hand.getAndResetGrabDeltaVelocity();
- glm::quat deltaRotation = _hand.getAndResetGrabRotation();
- const float GRAB_CONTROLLER_TURN_SCALING = 0.5f;
- glm::vec3 euler = safeEulerAngles(deltaRotation) * GRAB_CONTROLLER_TURN_SCALING;
- // Adjust body yaw by yaw from controller
- setOrientation(glm::angleAxis(-euler.y, glm::vec3(0, 1, 0)) * getOrientation());
- // Adjust head pitch from controller
- getHead().setPitch(getHead().getPitch() - euler.x);
-
_position += _velocity * deltaTime;
// update avatar skeleton and simulate hand and head
+ _hand.collideAgainstOurself();
_hand.simulate(deltaTime, true);
_skeletonModel.simulate(deltaTime);
_head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
@@ -364,7 +332,7 @@ void MyAvatar::simulate(float deltaTime) {
const float MAX_PITCH = 90.0f;
// Update avatar head rotation with sensor data
-void MyAvatar::updateFromGyros(bool turnWithHead) {
+void MyAvatar::updateFromGyros(float deltaTime) {
Faceshift* faceshift = Application::getInstance()->getFaceshift();
glm::vec3 estimatedPosition, estimatedRotation;
@@ -372,7 +340,7 @@ void MyAvatar::updateFromGyros(bool turnWithHead) {
estimatedPosition = faceshift->getHeadTranslation();
estimatedRotation = safeEulerAngles(faceshift->getHeadRotation());
// 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_MIN_YAW_TURN = 15.f;
const float FACESHIFT_MAX_YAW_TURN = 50.f;
@@ -387,11 +355,12 @@ void MyAvatar::updateFromGyros(bool turnWithHead) {
}
} else {
// restore rotation, lean to neutral positions
- const float RESTORE_RATE = 0.05f;
- _head.setYaw(glm::mix(_head.getYaw(), 0.0f, RESTORE_RATE));
- _head.setRoll(glm::mix(_head.getRoll(), 0.0f, RESTORE_RATE));
- _head.setLeanSideways(glm::mix(_head.getLeanSideways(), 0.0f, RESTORE_RATE));
- _head.setLeanForward(glm::mix(_head.getLeanForward(), 0.0f, RESTORE_RATE));
+ const float RESTORE_PERIOD = 1.f; // seconds
+ float restorePercentage = glm::clamp(deltaTime/RESTORE_PERIOD, 0.f, 1.f);
+ _head.setYaw(glm::mix(_head.getYaw(), 0.0f, restorePercentage));
+ _head.setRoll(glm::mix(_head.getRoll(), 0.0f, restorePercentage));
+ _head.setLeanSideways(glm::mix(_head.getLeanSideways(), 0.0f, restorePercentage));
+ _head.setLeanForward(glm::mix(_head.getLeanForward(), 0.0f, restorePercentage));
return;
}
@@ -809,39 +778,6 @@ void MyAvatar::updateThrust(float deltaTime) {
up;
}
}
- // Add thrust and rotation from hand controllers
- const float THRUST_MAG_HAND_JETS = THRUST_MAG_FWD;
- const float JOYSTICK_YAW_MAG = YAW_MAG;
- const float JOYSTICK_PITCH_MAG = PITCH_MAG * 0.5f;
- const int THRUST_CONTROLLER = 0;
- const int VIEW_CONTROLLER = 1;
- for (size_t i = 0; i < getHand().getPalms().size(); ++i) {
- PalmData& palm = getHand().getPalms()[i];
-
- // If the script hasn't captured this joystick, then let the default behavior work
- if (!Application::getInstance()->getControllerScriptingInterface()->isJoystickCaptured(palm.getSixenseID())) {
- if (palm.isActive() && (palm.getSixenseID() == THRUST_CONTROLLER)) {
- if (palm.getJoystickY() != 0.f) {
- FingerData& finger = palm.getFingers()[0];
- if (finger.isActive()) {
- }
- _thrust += front * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickY() * _thrustMultiplier * deltaTime;
- }
- if (palm.getJoystickX() != 0.f) {
- _thrust += right * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickX() * _thrustMultiplier * deltaTime;
- }
- } else if (palm.isActive() && (palm.getSixenseID() == VIEW_CONTROLLER)) {
- if (palm.getJoystickX() != 0.f) {
- _bodyYawDelta -= palm.getJoystickX() * JOYSTICK_YAW_MAG * deltaTime;
- }
- if (palm.getJoystickY() != 0.f) {
- getHand().setPitchUpdate(getHand().getPitchUpdate() +
- (palm.getJoystickY() * JOYSTICK_PITCH_MAG * deltaTime));
- }
- }
- }
-
- }
// Update speed brake status
const float MIN_SPEED_BRAKE_VELOCITY = _scale * 0.4f;
@@ -906,7 +842,6 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
}
}
-
void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
const float VOXEL_ELASTICITY = 0.4f;
const float VOXEL_DAMPING = 0.0f;
@@ -976,7 +911,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) {
// Reset detector for nearest avatar
@@ -986,7 +957,14 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// no need to compute a bunch of stuff if we have one or fewer avatars
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;
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
@@ -999,9 +977,25 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
if (_distanceToNearestAvatar > distance) {
_distanceToNearestAvatar = distance;
}
- float theirRadius = avatar->getHeight();
- if (distance < myRadius + theirRadius) {
- // TODO: Andrew to make avatar-avatar capsule collisions work here
+ float theirBoundingRadius = 0.5f * avatar->getHeight();
+ if (distance < myBoundingRadius + theirBoundingRadius) {
+ 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);
}
}
}
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 36448a5529..1bc5de204b 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -34,7 +34,7 @@ public:
void reset();
void update(float deltaTime);
void simulate(float deltaTime);
- void updateFromGyros(bool turnWithHead);
+ void updateFromGyros(float deltaTime);
void updateTransmitter(float deltaTime);
void render(bool forceRenderHead);
@@ -45,7 +45,6 @@ public:
// setters
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
- void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
void setLeanScale(float scale) { _leanScale = scale; }
void setGravity(glm::vec3 gravity);
@@ -78,9 +77,6 @@ public:
static void sendKillAvatar();
- // Set/Get update the thrust that will move the avatar around
- void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
- glm::vec3 getThrust() { return _thrust; };
void orbit(const glm::vec3& position, int deltaX, int deltaY);
@@ -93,9 +89,13 @@ public slots:
void increaseSize();
void decreaseSize();
void resetSize();
-
void sendIdentityPacket();
+ // Set/Get update the thrust that will move the avatar around
+ void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
+ glm::vec3 getThrust() { return _thrust; };
+ void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
+
private:
bool _mousePressed;
float _bodyPitchDelta;
diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp
index ac08c52b49..9bf2e0f727 100644
--- a/interface/src/avatar/SkeletonModel.cpp
+++ b/interface/src/avatar/SkeletonModel.cpp
@@ -21,9 +21,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
void SkeletonModel::simulate(float deltaTime) {
if (!isActive()) {
+ Model::simulate(deltaTime);
return;
}
-
setTranslation(_owningAvatar->getPosition());
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f));
const float MODEL_SCALE = 0.0006f;
@@ -36,23 +36,24 @@ void SkeletonModel::simulate(float deltaTime) {
HandData& hand = _owningAvatar->getHand();
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();
if (leftPalmIndex == -1) {
// no Leap data; set hands from mouse
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
- restoreRightHandPosition(HAND_RESTORATION_RATE);
+ restoreRightHandPosition(handRestorePercent);
} else {
applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition());
}
- restoreLeftHandPosition(HAND_RESTORATION_RATE);
+ restoreLeftHandPosition(handRestorePercent);
} else if (leftPalmIndex == rightPalmIndex) {
// right hand only
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
hand.getPalms()[leftPalmIndex]);
- restoreLeftHandPosition(HAND_RESTORATION_RATE);
+ restoreLeftHandPosition(handRestorePercent);
} else {
applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices,
diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp
index b4e7c4abc5..35512d88da 100644
--- a/interface/src/renderer/FBXReader.cpp
+++ b/interface/src/renderer/FBXReader.cpp
@@ -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.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();
QVariant defaultSpring = springs.value("default");
@@ -1430,6 +1432,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
boneDirection /= boneLength;
}
}
+ bool jointIsStatic = joint.freeLineage.isEmpty();
+ glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform);
float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix);
float totalWeight = 0.0f;
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(
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
@@ -1568,14 +1577,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
return geometry;
}
-FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
- QBuffer modelBuffer(const_cast(&model));
- modelBuffer.open(QIODevice::ReadOnly);
+QVariantHash readMapping(const QByteArray& data) {
+ QBuffer buffer(const_cast(&data));
+ buffer.open(QIODevice::ReadOnly);
+ return parseMapping(&buffer);
+}
- QBuffer mappingBuffer(const_cast(&mapping));
- mappingBuffer.open(QIODevice::ReadOnly);
-
- return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
+FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
+ QBuffer buffer(const_cast(&model));
+ buffer.open(QIODevice::ReadOnly);
+ return extractFBXGeometry(parseFBX(&buffer), mapping);
}
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h
index d700439460..8efb23c98c 100644
--- a/interface/src/renderer/FBXReader.h
+++ b/interface/src/renderer/FBXReader.h
@@ -159,13 +159,17 @@ public:
glm::vec3 neckPivot;
Extents bindExtents;
+ Extents staticExtents;
QVector attachments;
};
+/// Reads an FST mapping from the supplied data.
+QVariantHash readMapping(const QByteArray& data);
+
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
-FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
+FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
/// Reads SVO geometry from the supplied model data.
FBXGeometry readSVO(const QByteArray& model);
diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp
index 3526fa5050..78cc657018 100644
--- a/interface/src/renderer/GeometryCache.cpp
+++ b/interface/src/renderer/GeometryCache.cpp
@@ -7,11 +7,7 @@
#include
-// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
-#include "InterfaceConfig.h"
-
#include
-#include
#include
#include "Application.h"
@@ -298,46 +294,86 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons
if (geometry.isNull()) {
geometry = QSharedPointer(new NetworkGeometry(url, fallback.isValid() ?
getGeometry(fallback) : QSharedPointer()));
+ geometry->setLODParent(geometry);
_networkGeometry.insert(url, geometry);
}
return geometry;
}
-NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback) :
- _modelRequest(url),
- _modelReply(NULL),
- _mappingReply(NULL),
+const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
+
+NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback,
+ const QVariantHash& mapping, const QUrl& textureBase) :
+ _request(url),
+ _reply(NULL),
+ _mapping(mapping),
+ _textureBase(textureBase.isValid() ? textureBase : url),
_fallback(fallback),
- _attempts(0)
-{
+ _startedLoading(false),
+ _failedToLoad(false),
+ _attempts(0) {
+
if (!url.isValid()) {
return;
}
- _modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
- makeModelRequest();
+ _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
- QUrl mappingURL = url;
- QString path = url.path();
- mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
- QNetworkRequest mappingRequest(mappingURL);
- mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
- _mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest);
-
- connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
- connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError()));
+ // if we already have a mapping (because we're an LOD), hold off on loading until we're requested
+ if (mapping.isEmpty()) {
+ makeRequest();
+ }
}
NetworkGeometry::~NetworkGeometry() {
- if (_modelReply != NULL) {
- delete _modelReply;
+ if (_reply != NULL) {
+ delete _reply;
}
- if (_mappingReply != NULL) {
- delete _mappingReply;
+}
+
+QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const {
+ if (_lodParent.data() != this) {
+ return _lodParent.data()->getLODOrFallback(distance, hysteresis);
}
- foreach (const NetworkMesh& mesh, _meshes) {
- glDeleteBuffers(1, &mesh.indexBufferID);
- glDeleteBuffers(1, &mesh.vertexBufferID);
- }
+ if (_failedToLoad && _fallback) {
+ return _fallback;
+ }
+ QSharedPointer lod = _lodParent;
+ float lodDistance = 0.0f;
+ QMap >::const_iterator it = _lods.upperBound(distance);
+ if (it != _lods.constBegin()) {
+ it = it - 1;
+ lod = it.value();
+ lodDistance = it.key();
+ }
+ if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
+ // if we previously selected a different distance, make sure we've moved far enough to justify switching
+ const float HYSTERESIS_PROPORTION = 0.1f;
+ if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
+ return getLODOrFallback(hysteresis, hysteresis);
+ }
+ }
+ if (lod->isLoaded()) {
+ hysteresis = lodDistance;
+ return lod;
+ }
+ // if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one
+ if (!lod->_startedLoading) {
+ lod->makeRequest();
+ }
+ float closestDistance = FLT_MAX;
+ if (isLoaded()) {
+ lod = _lodParent;
+ closestDistance = distance;
+ }
+ for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
+ float distanceToLOD = glm::abs(distance - it.key());
+ if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
+ lod = it.value();
+ closestDistance = distanceToLOD;
+ }
+ }
+ hysteresis = NO_HYSTERESIS;
+ return lod;
}
glm::vec4 NetworkGeometry::computeAverageColor() const {
@@ -364,20 +400,167 @@ glm::vec4 NetworkGeometry::computeAverageColor() const {
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
}
-void NetworkGeometry::makeModelRequest() {
- _modelReply = Application::getInstance()->getNetworkAccessManager()->get(_modelRequest);
+void NetworkGeometry::makeRequest() {
+ _startedLoading = true;
+ _reply = Application::getInstance()->getNetworkAccessManager()->get(_request);
- connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
- connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
+ connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
+ connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
}
-void NetworkGeometry::handleModelReplyError() {
- QDebug debug = qDebug() << _modelReply->errorString();
+void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
+ if (!_reply->isFinished()) {
+ return;
+ }
- QNetworkReply::NetworkError error = _modelReply->error();
- _modelReply->disconnect(this);
- _modelReply->deleteLater();
- _modelReply = NULL;
+ QUrl url = _reply->url();
+ QByteArray data = _reply->readAll();
+ _reply->disconnect(this);
+ _reply->deleteLater();
+ _reply = NULL;
+
+ if (url.path().toLower().endsWith(".fst")) {
+ // it's a mapping file; parse it and get the mesh filename
+ _mapping = readMapping(data);
+ QString filename = _mapping.value("filename").toString();
+ if (filename.isNull()) {
+ qDebug() << "Mapping file " << url << " has no filename.";
+ _failedToLoad = true;
+
+ } else {
+ QString texdir = _mapping.value("texdir").toString();
+ if (!texdir.isNull()) {
+ if (!texdir.endsWith('/')) {
+ texdir += '/';
+ }
+ _textureBase = url.resolved(texdir);
+ }
+ QVariantHash lods = _mapping.value("lod").toHash();
+ for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
+ QSharedPointer geometry(new NetworkGeometry(url.resolved(it.key()),
+ QSharedPointer(), _mapping, _textureBase));
+ geometry->setLODParent(_lodParent);
+ _lods.insert(it.value().toFloat(), geometry);
+ }
+ _request.setUrl(url.resolved(filename));
+
+ // make the request immediately only if we have no LODs to switch between
+ _startedLoading = false;
+ if (_lods.isEmpty()) {
+ makeRequest();
+ }
+ }
+ return;
+ }
+
+ try {
+ _geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping);
+
+ } catch (const QString& error) {
+ qDebug() << "Error reading " << url << ": " << error;
+ _failedToLoad = true;
+ return;
+ }
+
+ foreach (const FBXMesh& mesh, _geometry.meshes) {
+ NetworkMesh networkMesh = { QOpenGLBuffer(QOpenGLBuffer::IndexBuffer), QOpenGLBuffer(QOpenGLBuffer::VertexBuffer) };
+
+ int totalIndices = 0;
+ foreach (const FBXMeshPart& part, mesh.parts) {
+ NetworkMeshPart networkPart;
+ if (!part.diffuseFilename.isEmpty()) {
+ networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
+ _textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
+ }
+ if (!part.normalFilename.isEmpty()) {
+ networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
+ _textureBase.resolved(QUrl(part.normalFilename)), true);
+ }
+ networkMesh.parts.append(networkPart);
+
+ totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
+ }
+
+ networkMesh.indexBuffer.create();
+ networkMesh.indexBuffer.bind();
+ networkMesh.indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
+ networkMesh.indexBuffer.allocate(totalIndices * sizeof(int));
+ int offset = 0;
+ foreach (const FBXMeshPart& part, mesh.parts) {
+ glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
+ part.quadIndices.constData());
+ offset += part.quadIndices.size() * sizeof(int);
+ glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
+ part.triangleIndices.constData());
+ offset += part.triangleIndices.size() * sizeof(int);
+ }
+ networkMesh.indexBuffer.release();
+
+ networkMesh.vertexBuffer.create();
+ networkMesh.vertexBuffer.bind();
+ networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
+
+ // if we don't need to do any blending or springing, then the positions/normals can be static
+ if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
+ int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
+ int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
+ int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
+ int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
+ int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
+ int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
+
+ networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
+ networkMesh.vertexBuffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(normalsOffset, mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(tangentsOffset, mesh.tangents.constData(),
+ mesh.tangents.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
+ mesh.texCoords.size() * sizeof(glm::vec2));
+ networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
+ mesh.clusterIndices.size() * sizeof(glm::vec4));
+ networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
+ mesh.clusterWeights.size() * sizeof(glm::vec4));
+
+ // if there's no springiness, then the cluster indices/weights can be static
+ } else if (mesh.springiness == 0.0f) {
+ int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
+ int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
+ int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
+ int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
+ networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
+ networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
+ mesh.texCoords.size() * sizeof(glm::vec2));
+ networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
+ mesh.clusterIndices.size() * sizeof(glm::vec4));
+ networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
+ mesh.clusterWeights.size() * sizeof(glm::vec4));
+
+ } else {
+ int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
+ int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
+ networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2));
+ networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
+ networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
+ mesh.texCoords.size() * sizeof(glm::vec2));
+ }
+
+ networkMesh.vertexBuffer.release();
+
+ _meshes.append(networkMesh);
+ }
+}
+
+void NetworkGeometry::handleReplyError() {
+ QDebug debug = qDebug() << _reply->errorString();
+
+ QNetworkReply::NetworkError error = _reply->error();
+ _reply->disconnect(this);
+ _reply->deleteLater();
+ _reply = NULL;
// retry for certain types of failures
switch (error) {
@@ -394,164 +577,19 @@ void NetworkGeometry::handleModelReplyError() {
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
- QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
+ QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
debug << " -- retrying...";
return;
}
// fall through to final failure
}
default:
- maybeLoadFallback();
+ _failedToLoad = true;
break;
}
}
-void NetworkGeometry::handleMappingReplyError() {
- _mappingReply->disconnect(this);
- _mappingReply->deleteLater();
- _mappingReply = NULL;
-
- maybeReadModelWithMapping();
-}
-
-void NetworkGeometry::maybeReadModelWithMapping() {
- if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) {
- return;
- }
-
- QUrl url = _modelReply->url();
- QByteArray model = _modelReply->readAll();
- _modelReply->disconnect(this);
- _modelReply->deleteLater();
- _modelReply = NULL;
-
- QByteArray mapping;
- if (_mappingReply != NULL) {
- mapping = _mappingReply->readAll();
- _mappingReply->disconnect(this);
- _mappingReply->deleteLater();
- _mappingReply = NULL;
- }
-
- try {
- _geometry = url.path().toLower().endsWith(".svo") ? readSVO(model) : readFBX(model, mapping);
-
- } catch (const QString& error) {
- qDebug() << "Error reading " << url << ": " << error;
- maybeLoadFallback();
- return;
- }
-
- foreach (const FBXMesh& mesh, _geometry.meshes) {
- NetworkMesh networkMesh;
-
- int totalIndices = 0;
- foreach (const FBXMeshPart& part, mesh.parts) {
- NetworkMeshPart networkPart;
- QString basePath = url.path();
- basePath = basePath.left(basePath.lastIndexOf('/') + 1);
- if (!part.diffuseFilename.isEmpty()) {
- url.setPath(basePath + part.diffuseFilename);
- networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, false, mesh.isEye);
- }
- if (!part.normalFilename.isEmpty()) {
- url.setPath(basePath + part.normalFilename);
- networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url, true);
- }
- networkMesh.parts.append(networkPart);
-
- totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
- }
-
- glGenBuffers(1, &networkMesh.indexBufferID);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW);
- int offset = 0;
- foreach (const FBXMeshPart& part, mesh.parts) {
- glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
- part.quadIndices.constData());
- offset += part.quadIndices.size() * sizeof(int);
- glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
- part.triangleIndices.constData());
- offset += part.triangleIndices.size() * sizeof(int);
- }
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-
- glGenBuffers(1, &networkMesh.vertexBufferID);
- glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
-
- // if we don't need to do any blending or springing, then the positions/normals can be static
- if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
- int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
- int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
- int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
- int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
- int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
- int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
- glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
- NULL, GL_STATIC_DRAW);
- glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());
- glBufferSubData(GL_ARRAY_BUFFER, normalsOffset, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
- glBufferSubData(GL_ARRAY_BUFFER, tangentsOffset, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
- glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
- glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
- mesh.texCoords.constData());
- glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
- mesh.clusterIndices.constData());
- glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
- mesh.clusterWeights.constData());
-
- // if there's no springiness, then the cluster indices/weights can be static
- } else if (mesh.springiness == 0.0f) {
- int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
- int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
- int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
- int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
- glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
- NULL, GL_STATIC_DRAW);
- glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
- glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
- glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
- glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
- mesh.clusterIndices.constData());
- glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
- mesh.clusterWeights.constData());
-
- } else {
- int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
- int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
- glBufferData(GL_ARRAY_BUFFER, texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
- glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
- glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
- glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
- mesh.texCoords.constData());
- }
-
- glBindBuffer(GL_ARRAY_BUFFER, 0);
-
- _meshes.append(networkMesh);
- }
-
- emit loaded();
-}
-
-void NetworkGeometry::loadFallback() {
- _geometry = _fallback->_geometry;
- _meshes = _fallback->_meshes;
- emit loaded();
-}
-
-void NetworkGeometry::maybeLoadFallback() {
- if (_fallback) {
- if (_fallback->isLoaded()) {
- loadFallback();
- } else {
- connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback()));
- }
- }
-}
-
bool NetworkMeshPart::isTranslucent() const {
return diffuseTexture && diffuseTexture->isTranslucent();
}
diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h
index 618796e907..ef53fe9c3e 100644
--- a/interface/src/renderer/GeometryCache.h
+++ b/interface/src/renderer/GeometryCache.h
@@ -9,17 +9,20 @@
#ifndef __interface__GeometryCache__
#define __interface__GeometryCache__
+// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
#include
+#include
#include
#include
+#include
#include
#include
#include "FBXReader.h"
-#include "InterfaceConfig.h"
class QNetworkReply;
-class QOpenGLBuffer;
class NetworkGeometry;
class NetworkMesh;
@@ -59,41 +62,52 @@ class NetworkGeometry : public QObject {
public:
- NetworkGeometry(const QUrl& url, const QSharedPointer& fallback);
+ /// A hysteresis value indicating that we have no state memory.
+ static const float NO_HYSTERESIS;
+
+ NetworkGeometry(const QUrl& url, const QSharedPointer& fallback,
+ const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
~NetworkGeometry();
+ /// Checks whether the geometry is fulled loaded.
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
+ /// Returns a pointer to the geometry appropriate for the specified distance.
+ /// \param hysteresis a hysteresis parameter that prevents rapid model switching
+ QSharedPointer getLODOrFallback(float distance, float& hysteresis) const;
+
const FBXGeometry& getFBXGeometry() const { return _geometry; }
const QVector& getMeshes() const { return _meshes; }
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
-signals:
-
- void loaded();
-
private slots:
- void makeModelRequest();
- void handleModelReplyError();
- void handleMappingReplyError();
- void maybeReadModelWithMapping();
- void loadFallback();
+ void makeRequest();
+ void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+ void handleReplyError();
private:
- void maybeLoadFallback();
+ friend class GeometryCache;
- QNetworkRequest _modelRequest;
- QNetworkReply* _modelReply;
- QNetworkReply* _mappingReply;
+ void setLODParent(const QWeakPointer& lodParent) { _lodParent = lodParent; }
+
+ QNetworkRequest _request;
+ QNetworkReply* _reply;
+ QVariantHash _mapping;
+ QUrl _textureBase;
QSharedPointer _fallback;
+ bool _startedLoading;
+ bool _failedToLoad;
int _attempts;
+ QMap > _lods;
FBXGeometry _geometry;
QVector _meshes;
+
+ QWeakPointer _lodParent;
};
/// The state associated with a single mesh part.
@@ -110,8 +124,8 @@ public:
class NetworkMesh {
public:
- GLuint indexBufferID;
- GLuint vertexBufferID;
+ QOpenGLBuffer indexBuffer;
+ QOpenGLBuffer vertexBuffer;
QVector parts;
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index b14ed1036d..8c00842ea2 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -90,6 +90,16 @@ void Model::reset() {
}
void Model::simulate(float deltaTime) {
+ // update our LOD
+ if (_geometry) {
+ QSharedPointer geometry = _geometry->getLODOrFallback(glm::distance(_translation,
+ Application::getInstance()->getCamera()->getPosition()), _lodHysteresis);
+ if (_geometry != geometry) {
+ deleteGeometry();
+ _dilatedTextures.clear();
+ _geometry = geometry;
+ }
+ }
if (!isActive()) {
return;
}
@@ -305,6 +315,15 @@ Extents Model::getBindExtents() const {
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 {
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).parentIndex : -1;
}
@@ -400,8 +419,9 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
// delete our local geometry and custom textures
deleteGeometry();
_dilatedTextures.clear();
+ _lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
- _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
+ _baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
}
glm::vec4 Model::computeAverageColor() const {
@@ -468,7 +488,10 @@ bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetra
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
startRadius, endRadius, 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;
}
outerContinue: ;
@@ -713,7 +736,19 @@ void Model::renderCollisionProxies(float alpha) {
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& 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)
// but unmovable joints (such as torso) cannot be influenced at all.
glm::vec3 jointPosition(0.f);
@@ -737,11 +772,10 @@ bool Model::poke(ModelCollisionInfo& collision) {
getJointPosition(jointIndex, end);
glm::vec3 newEnd = start + glm::angleAxis(glm::degrees(angle), axis) * (end - start);
// try to move it
- return setJointPosition(jointIndex, newEnd, -1, true);
+ setJointPosition(jointIndex, newEnd, -1, true);
}
}
}
- return false;
}
void Model::deleteGeometry() {
@@ -768,13 +802,17 @@ void Model::renderMeshes(float alpha, bool translucent) {
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
continue;
}
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
-
+ const_cast(networkMesh.indexBuffer).bind();
+
const FBXMesh& mesh = geometry.meshes.at(i);
int vertexCount = mesh.vertices.size();
+ if (vertexCount == 0) {
+ // sanity check
+ continue;
+ }
+
+ const_cast(networkMesh.vertexBuffer).bind();
- glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
-
ProgramObject* program = &_program;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations;
@@ -881,11 +919,11 @@ void Model::renderMeshes(float alpha, bool translucent) {
qint64 offset = 0;
for (int j = 0; j < networkMesh.parts.size(); j++) {
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
+ const FBXMeshPart& part = mesh.parts.at(j);
if (networkPart.isTranslucent() != translucent) {
+ offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int);
continue;
}
- const FBXMeshPart& part = mesh.parts.at(j);
-
// apply material properties
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index 423b2a4c81..41435c00b7 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -66,6 +66,9 @@ public:
/// Returns the extents of the model in its bind pose.
Extents getBindExtents() const;
+
+ /// Returns the extents of the unmovable joints of the model.
+ Extents getStaticExtents() const;
/// Returns a reference to the shared geometry.
const QSharedPointer& getGeometry() const { return _geometry; }
@@ -164,9 +167,12 @@ public:
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
- /// \return true if collision affects the Model
- bool poke(ModelCollisionInfo& collisionInfo);
+ /// Use the collisionInfo to affect the model
+ void applyCollision(ModelCollisionInfo& collisionInfo);
protected:
@@ -230,6 +236,9 @@ private:
void deleteGeometry();
void renderMeshes(float alpha, bool translucent);
+ QSharedPointer _baseGeometry;
+ float _lodHysteresis;
+
float _pupilDilation;
std::vector _blendshapeCoefficients;
diff --git a/interface/src/ui/Base3DOverlay.cpp b/interface/src/ui/Base3DOverlay.cpp
new file mode 100644
index 0000000000..67e7ea25f2
--- /dev/null
+++ b/interface/src/ui/Base3DOverlay.cpp
@@ -0,0 +1,61 @@
+//
+// Base3DOverlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+
+#include "Base3DOverlay.h"
+#include "TextRenderer.h"
+
+const glm::vec3 DEFAULT_POSITION = glm::vec3(0.0f, 0.0f, 0.0f);
+const float DEFAULT_LINE_WIDTH = 1.0f;
+
+Base3DOverlay::Base3DOverlay() :
+ _position(DEFAULT_POSITION),
+ _lineWidth(DEFAULT_LINE_WIDTH)
+{
+}
+
+Base3DOverlay::~Base3DOverlay() {
+}
+
+void Base3DOverlay::setProperties(const QScriptValue& properties) {
+ Overlay::setProperties(properties);
+
+ QScriptValue position = properties.property("position");
+
+ // if "position" property was not there, check to see if they included aliases: start, point, p1
+ if (!position.isValid()) {
+ position = properties.property("start");
+ if (!position.isValid()) {
+ position = properties.property("p1");
+ if (!position.isValid()) {
+ position = properties.property("point");
+ }
+ }
+ }
+
+ if (position.isValid()) {
+ QScriptValue x = position.property("x");
+ QScriptValue y = position.property("y");
+ QScriptValue z = position.property("z");
+ if (x.isValid() && y.isValid() && z.isValid()) {
+ glm::vec3 newPosition;
+ newPosition.x = x.toVariant().toFloat();
+ newPosition.y = y.toVariant().toFloat();
+ newPosition.z = z.toVariant().toFloat();
+ setPosition(newPosition);
+ }
+ }
+
+ if (properties.property("lineWidth").isValid()) {
+ setLineWidth(properties.property("lineWidth").toVariant().toFloat());
+ }
+}
diff --git a/interface/src/ui/Base3DOverlay.h b/interface/src/ui/Base3DOverlay.h
new file mode 100644
index 0000000000..286193393c
--- /dev/null
+++ b/interface/src/ui/Base3DOverlay.h
@@ -0,0 +1,36 @@
+//
+// Base3DOverlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Base3DOverlay__
+#define __interface__Base3DOverlay__
+
+#include "Overlay.h"
+
+class Base3DOverlay : public Overlay {
+ Q_OBJECT
+
+public:
+ Base3DOverlay();
+ ~Base3DOverlay();
+
+ // getters
+ const glm::vec3& getPosition() const { return _position; }
+ float getLineWidth() const { return _lineWidth; }
+
+ // setters
+ void setPosition(const glm::vec3& position) { _position = position; }
+ void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
+
+ virtual void setProperties(const QScriptValue& properties);
+
+protected:
+ glm::vec3 _position;
+ float _lineWidth;
+};
+
+
+#endif /* defined(__interface__Base3DOverlay__) */
diff --git a/interface/src/ui/Cube3DOverlay.cpp b/interface/src/ui/Cube3DOverlay.cpp
new file mode 100644
index 0000000000..992a18e451
--- /dev/null
+++ b/interface/src/ui/Cube3DOverlay.cpp
@@ -0,0 +1,44 @@
+//
+// Cube3DOverlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+
+#include "Cube3DOverlay.h"
+
+Cube3DOverlay::Cube3DOverlay() {
+}
+
+Cube3DOverlay::~Cube3DOverlay() {
+}
+
+void Cube3DOverlay::render() {
+ if (!_visible) {
+ return; // do nothing if we're not visible
+ }
+
+ const float MAX_COLOR = 255;
+ glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
+
+
+ glDisable(GL_LIGHTING);
+ glPushMatrix();
+ glTranslatef(_position.x + _size * 0.5f,
+ _position.y + _size * 0.5f,
+ _position.z + _size * 0.5f);
+ glLineWidth(_lineWidth);
+ if (_isSolid) {
+ glutSolidCube(_size);
+ } else {
+ glutWireCube(_size);
+ }
+ glPopMatrix();
+
+}
diff --git a/interface/src/ui/Cube3DOverlay.h b/interface/src/ui/Cube3DOverlay.h
new file mode 100644
index 0000000000..a1705d47d0
--- /dev/null
+++ b/interface/src/ui/Cube3DOverlay.h
@@ -0,0 +1,23 @@
+//
+// Cube3DOverlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Cube3DOverlay__
+#define __interface__Cube3DOverlay__
+
+#include "Volume3DOverlay.h"
+
+class Cube3DOverlay : public Volume3DOverlay {
+ Q_OBJECT
+
+public:
+ Cube3DOverlay();
+ ~Cube3DOverlay();
+ virtual void render();
+};
+
+
+#endif /* defined(__interface__Cube3DOverlay__) */
diff --git a/interface/src/ui/ImageOverlay.cpp b/interface/src/ui/ImageOverlay.cpp
new file mode 100644
index 0000000000..178383749b
--- /dev/null
+++ b/interface/src/ui/ImageOverlay.cpp
@@ -0,0 +1,145 @@
+//
+// ImageOverlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+#include
+#include
+
+#include "ImageOverlay.h"
+
+ImageOverlay::ImageOverlay() :
+ _textureID(0),
+ _renderImage(false),
+ _textureBound(false),
+ _wantClipFromImage(false)
+{
+}
+
+ImageOverlay::~ImageOverlay() {
+ if (_parent && _textureID) {
+ // do we need to call this?
+ //_parent->deleteTexture(_textureID);
+ }
+}
+
+// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
+void ImageOverlay::setImageURL(const QUrl& url) {
+ // TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
+ QNetworkAccessManager* manager = new QNetworkAccessManager(this);
+ connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
+ manager->get(QNetworkRequest(url));
+}
+
+void ImageOverlay::replyFinished(QNetworkReply* reply) {
+
+ // replace our byte array with the downloaded data
+ QByteArray rawData = reply->readAll();
+ _textureImage.loadFromData(rawData);
+ _renderImage = true;
+
+}
+
+void ImageOverlay::render() {
+ if (!_visible) {
+ return; // do nothing if we're not visible
+ }
+ if (_renderImage && !_textureBound) {
+ _textureID = _parent->bindTexture(_textureImage);
+ _textureBound = true;
+ }
+
+ if (_renderImage) {
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, _textureID);
+ }
+ const float MAX_COLOR = 255;
+ glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
+
+ float imageWidth = _textureImage.width();
+ float imageHeight = _textureImage.height();
+
+ QRect fromImage;
+ if (_wantClipFromImage) {
+ fromImage = _fromImage;
+ } else {
+ fromImage.setX(0);
+ fromImage.setY(0);
+ fromImage.setWidth(imageWidth);
+ fromImage.setHeight(imageHeight);
+ }
+ float x = fromImage.x() / imageWidth;
+ float y = fromImage.y() / imageHeight;
+ float w = fromImage.width() / imageWidth; // ?? is this what we want? not sure
+ float h = fromImage.height() / imageHeight;
+
+ glBegin(GL_QUADS);
+ if (_renderImage) {
+ glTexCoord2f(x, 1.0f - y);
+ }
+ glVertex2f(_bounds.left(), _bounds.top());
+
+ if (_renderImage) {
+ glTexCoord2f(x + w, 1.0f - y);
+ }
+ glVertex2f(_bounds.right(), _bounds.top());
+
+ if (_renderImage) {
+ glTexCoord2f(x + w, 1.0f - (y + h));
+ }
+ glVertex2f(_bounds.right(), _bounds.bottom());
+
+ if (_renderImage) {
+ glTexCoord2f(x, 1.0f - (y + h));
+ }
+ glVertex2f(_bounds.left(), _bounds.bottom());
+ glEnd();
+ if (_renderImage) {
+ glDisable(GL_TEXTURE_2D);
+ }
+}
+
+void ImageOverlay::setProperties(const QScriptValue& properties) {
+ Overlay2D::setProperties(properties);
+
+ QScriptValue subImageBounds = properties.property("subImage");
+ if (subImageBounds.isValid()) {
+ QRect oldSubImageRect = _fromImage;
+ QRect subImageRect = _fromImage;
+ if (subImageBounds.property("x").isValid()) {
+ subImageRect.setX(subImageBounds.property("x").toVariant().toInt());
+ } else {
+ subImageRect.setX(oldSubImageRect.x());
+ }
+ if (subImageBounds.property("y").isValid()) {
+ subImageRect.setY(subImageBounds.property("y").toVariant().toInt());
+ } else {
+ subImageRect.setY(oldSubImageRect.y());
+ }
+ if (subImageBounds.property("width").isValid()) {
+ subImageRect.setWidth(subImageBounds.property("width").toVariant().toInt());
+ } else {
+ subImageRect.setWidth(oldSubImageRect.width());
+ }
+ if (subImageBounds.property("height").isValid()) {
+ subImageRect.setHeight(subImageBounds.property("height").toVariant().toInt());
+ } else {
+ subImageRect.setHeight(oldSubImageRect.height());
+ }
+ setClipFromSource(subImageRect);
+ }
+
+ QScriptValue imageURL = properties.property("imageURL");
+ if (imageURL.isValid()) {
+ setImageURL(imageURL.toVariant().toString());
+ }
+}
+
+
diff --git a/interface/src/ui/ImageOverlay.h b/interface/src/ui/ImageOverlay.h
new file mode 100644
index 0000000000..77cac3b3c6
--- /dev/null
+++ b/interface/src/ui/ImageOverlay.h
@@ -0,0 +1,60 @@
+//
+// ImageOverlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__ImageOverlay__
+#define __interface__ImageOverlay__
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "Overlay.h"
+#include "Overlay2D.h"
+
+class ImageOverlay : public Overlay2D {
+ Q_OBJECT
+
+public:
+ ImageOverlay();
+ ~ImageOverlay();
+ virtual void render();
+
+ // getters
+ const QRect& getClipFromSource() const { return _fromImage; }
+ const QUrl& getImageURL() const { return _imageURL; }
+
+ // setters
+ void setClipFromSource(const QRect& bounds) { _fromImage = bounds; _wantClipFromImage = true; }
+ void setImageURL(const QUrl& url);
+ virtual void setProperties(const QScriptValue& properties);
+
+private slots:
+ void replyFinished(QNetworkReply* reply); // we actually want to hide this...
+
+private:
+
+ QUrl _imageURL;
+ QImage _textureImage;
+ GLuint _textureID;
+ QRect _fromImage; // where from in the image to sample
+ bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle
+ bool _textureBound; // has the texture been bound
+ bool _wantClipFromImage;
+};
+
+
+#endif /* defined(__interface__ImageOverlay__) */
diff --git a/interface/src/ui/Line3DOverlay.cpp b/interface/src/ui/Line3DOverlay.cpp
new file mode 100644
index 0000000000..c357233329
--- /dev/null
+++ b/interface/src/ui/Line3DOverlay.cpp
@@ -0,0 +1,60 @@
+//
+// Line3DOverlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include "Line3DOverlay.h"
+
+
+Line3DOverlay::Line3DOverlay() {
+}
+
+Line3DOverlay::~Line3DOverlay() {
+}
+
+void Line3DOverlay::render() {
+ if (!_visible) {
+ return; // do nothing if we're not visible
+ }
+
+ const float MAX_COLOR = 255;
+ glDisable(GL_LIGHTING);
+ glLineWidth(_lineWidth);
+ glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
+
+ glBegin(GL_LINES);
+ glVertex3f(_position.x, _position.y, _position.z);
+ glVertex3f(_end.x, _end.y, _end.z);
+ glEnd();
+ glEnable(GL_LIGHTING);
+}
+
+void Line3DOverlay::setProperties(const QScriptValue& properties) {
+ Base3DOverlay::setProperties(properties);
+
+ QScriptValue end = properties.property("end");
+ // if "end" property was not there, check to see if they included aliases: endPoint, or p2
+ if (!end.isValid()) {
+ end = properties.property("endPoint");
+ if (!end.isValid()) {
+ end = properties.property("p2");
+ }
+ }
+ if (end.isValid()) {
+ QScriptValue x = end.property("x");
+ QScriptValue y = end.property("y");
+ QScriptValue z = end.property("z");
+ if (x.isValid() && y.isValid() && z.isValid()) {
+ glm::vec3 newEnd;
+ newEnd.x = x.toVariant().toFloat();
+ newEnd.y = y.toVariant().toFloat();
+ newEnd.z = z.toVariant().toFloat();
+ setEnd(newEnd);
+ }
+ }
+}
diff --git a/interface/src/ui/Line3DOverlay.h b/interface/src/ui/Line3DOverlay.h
new file mode 100644
index 0000000000..d52b639d59
--- /dev/null
+++ b/interface/src/ui/Line3DOverlay.h
@@ -0,0 +1,34 @@
+//
+// Line3DOverlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Line3DOverlay__
+#define __interface__Line3DOverlay__
+
+#include "Base3DOverlay.h"
+
+class Line3DOverlay : public Base3DOverlay {
+ Q_OBJECT
+
+public:
+ Line3DOverlay();
+ ~Line3DOverlay();
+ virtual void render();
+
+ // getters
+ const glm::vec3& getEnd() const { return _end; }
+
+ // setters
+ void setEnd(const glm::vec3& end) { _end = end; }
+
+ virtual void setProperties(const QScriptValue& properties);
+
+protected:
+ glm::vec3 _end;
+};
+
+
+#endif /* defined(__interface__Line3DOverlay__) */
diff --git a/interface/src/ui/LodToolsDialog.cpp b/interface/src/ui/LodToolsDialog.cpp
index 788f7e5561..4cf4a29bf1 100644
--- a/interface/src/ui/LodToolsDialog.cpp
+++ b/interface/src/ui/LodToolsDialog.cpp
@@ -121,6 +121,12 @@ LodToolsDialog::~LodToolsDialog() {
delete _boundaryLevelAdjust;
}
+void LodToolsDialog::reloadSliders() {
+ _lodSize->setValue(Menu::getInstance()->getVoxelSizeScale() / TREE_SCALE);
+ _boundaryLevelAdjust->setValue(Menu::getInstance()->getBoundaryLevelAdjust());
+ _feedback->setText(getFeedbackText());
+}
+
void LodToolsDialog::sizeScaleValueChanged(int value) {
float realValue = value * TREE_SCALE;
Menu::getInstance()->setVoxelSizeScale(realValue);
diff --git a/interface/src/ui/LodToolsDialog.h b/interface/src/ui/LodToolsDialog.h
index ee14196188..ee96cffd7e 100644
--- a/interface/src/ui/LodToolsDialog.h
+++ b/interface/src/ui/LodToolsDialog.h
@@ -28,6 +28,7 @@ public slots:
void sizeScaleValueChanged(int value);
void boundaryLevelValueChanged(int value);
void resetClicked(bool checked);
+ void reloadSliders();
protected:
diff --git a/interface/src/ui/Overlay.cpp b/interface/src/ui/Overlay.cpp
new file mode 100644
index 0000000000..40da2253f4
--- /dev/null
+++ b/interface/src/ui/Overlay.cpp
@@ -0,0 +1,55 @@
+//
+// Overlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+#include
+#include
+
+#include "Overlay.h"
+
+
+Overlay::Overlay() :
+ _parent(NULL),
+ _alpha(DEFAULT_ALPHA),
+ _color(DEFAULT_BACKGROUND_COLOR),
+ _visible(true)
+{
+}
+
+void Overlay::init(QGLWidget* parent) {
+ _parent = parent;
+}
+
+
+Overlay::~Overlay() {
+}
+
+void Overlay::setProperties(const QScriptValue& properties) {
+ QScriptValue color = properties.property("color");
+ if (color.isValid()) {
+ QScriptValue red = color.property("red");
+ QScriptValue green = color.property("green");
+ QScriptValue blue = color.property("blue");
+ if (red.isValid() && green.isValid() && blue.isValid()) {
+ _color.red = red.toVariant().toInt();
+ _color.green = green.toVariant().toInt();
+ _color.blue = blue.toVariant().toInt();
+ }
+ }
+
+ if (properties.property("alpha").isValid()) {
+ setAlpha(properties.property("alpha").toVariant().toFloat());
+ }
+
+ if (properties.property("visible").isValid()) {
+ setVisible(properties.property("visible").toVariant().toBool());
+ }
+}
diff --git a/interface/src/ui/Overlay.h b/interface/src/ui/Overlay.h
new file mode 100644
index 0000000000..df898ec741
--- /dev/null
+++ b/interface/src/ui/Overlay.h
@@ -0,0 +1,53 @@
+//
+// Overlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Overlay__
+#define __interface__Overlay__
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+#include
+#include
+
+#include // for xColor
+
+const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 };
+const float DEFAULT_ALPHA = 0.7f;
+
+class Overlay : public QObject {
+ Q_OBJECT
+
+public:
+ Overlay();
+ ~Overlay();
+ void init(QGLWidget* parent);
+ virtual void render() = 0;
+
+ // getters
+ bool getVisible() const { return _visible; }
+ const xColor& getColor() const { return _color; }
+ float getAlpha() const { return _alpha; }
+
+ // setters
+ void setVisible(bool visible) { _visible = visible; }
+ void setColor(const xColor& color) { _color = color; }
+ void setAlpha(float alpha) { _alpha = alpha; }
+
+ virtual void setProperties(const QScriptValue& properties);
+
+protected:
+ QGLWidget* _parent;
+ float _alpha;
+ xColor _color;
+ bool _visible; // should the overlay be drawn at all
+};
+
+
+#endif /* defined(__interface__Overlay__) */
diff --git a/interface/src/ui/Overlay2D.cpp b/interface/src/ui/Overlay2D.cpp
new file mode 100644
index 0000000000..0c459811c4
--- /dev/null
+++ b/interface/src/ui/Overlay2D.cpp
@@ -0,0 +1,63 @@
+//
+// Overlay2D.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+#include
+#include
+
+#include "Overlay2D.h"
+
+
+Overlay2D::Overlay2D() {
+}
+
+Overlay2D::~Overlay2D() {
+}
+
+void Overlay2D::setProperties(const QScriptValue& properties) {
+ Overlay::setProperties(properties);
+
+ QScriptValue bounds = properties.property("bounds");
+ if (bounds.isValid()) {
+ QRect boundsRect;
+ boundsRect.setX(bounds.property("x").toVariant().toInt());
+ boundsRect.setY(bounds.property("y").toVariant().toInt());
+ boundsRect.setWidth(bounds.property("width").toVariant().toInt());
+ boundsRect.setHeight(bounds.property("height").toVariant().toInt());
+ setBounds(boundsRect);
+ } else {
+ QRect oldBounds = getBounds();
+ QRect newBounds = oldBounds;
+
+ if (properties.property("x").isValid()) {
+ newBounds.setX(properties.property("x").toVariant().toInt());
+ } else {
+ newBounds.setX(oldBounds.x());
+ }
+ if (properties.property("y").isValid()) {
+ newBounds.setY(properties.property("y").toVariant().toInt());
+ } else {
+ newBounds.setY(oldBounds.y());
+ }
+ if (properties.property("width").isValid()) {
+ newBounds.setWidth(properties.property("width").toVariant().toInt());
+ } else {
+ newBounds.setWidth(oldBounds.width());
+ }
+ if (properties.property("height").isValid()) {
+ newBounds.setHeight(properties.property("height").toVariant().toInt());
+ } else {
+ newBounds.setHeight(oldBounds.height());
+ }
+ setBounds(newBounds);
+ //qDebug() << "set bounds to " << getBounds();
+ }
+}
diff --git a/interface/src/ui/Overlay2D.h b/interface/src/ui/Overlay2D.h
new file mode 100644
index 0000000000..3da8f8bca4
--- /dev/null
+++ b/interface/src/ui/Overlay2D.h
@@ -0,0 +1,51 @@
+//
+// Overlay2D.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Overlay2D__
+#define __interface__Overlay2D__
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+#include
+#include
+
+#include // for xColor
+
+#include "Overlay.h"
+
+class Overlay2D : public Overlay {
+ Q_OBJECT
+
+public:
+ Overlay2D();
+ ~Overlay2D();
+
+ // getters
+ int getX() const { return _bounds.x(); }
+ int getY() const { return _bounds.y(); }
+ int getWidth() const { return _bounds.width(); }
+ int getHeight() const { return _bounds.height(); }
+ const QRect& getBounds() const { return _bounds; }
+
+ // setters
+ void setX(int x) { _bounds.setX(x); }
+ void setY(int y) { _bounds.setY(y); }
+ void setWidth(int width) { _bounds.setWidth(width); }
+ void setHeight(int height) { _bounds.setHeight(height); }
+ void setBounds(const QRect& bounds) { _bounds = bounds; }
+
+ virtual void setProperties(const QScriptValue& properties);
+
+protected:
+ QRect _bounds; // where on the screen to draw
+};
+
+
+#endif /* defined(__interface__Overlay2D__) */
diff --git a/interface/src/ui/Overlays.cpp b/interface/src/ui/Overlays.cpp
new file mode 100644
index 0000000000..c35c4fc5ec
--- /dev/null
+++ b/interface/src/ui/Overlays.cpp
@@ -0,0 +1,129 @@
+//
+// Overlays.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+
+#include "Cube3DOverlay.h"
+#include "ImageOverlay.h"
+#include "Line3DOverlay.h"
+#include "Overlays.h"
+#include "Sphere3DOverlay.h"
+#include "TextOverlay.h"
+
+
+unsigned int Overlays::_nextOverlayID = 1;
+
+Overlays::Overlays() {
+}
+
+Overlays::~Overlays() {
+}
+
+void Overlays::init(QGLWidget* parent) {
+ _parent = parent;
+}
+
+void Overlays::render2D() {
+ foreach(Overlay* thisOverlay, _overlays2D) {
+ thisOverlay->render();
+ }
+}
+
+void Overlays::render3D() {
+ foreach(Overlay* thisOverlay, _overlays3D) {
+ thisOverlay->render();
+ }
+}
+
+// TODO: make multi-threaded safe
+unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
+ unsigned int thisID = 0;
+ bool created = false;
+ bool is3D = false;
+ Overlay* thisOverlay = NULL;
+
+ if (type == "image") {
+ thisOverlay = new ImageOverlay();
+ thisOverlay->init(_parent);
+ thisOverlay->setProperties(properties);
+ created = true;
+ } else if (type == "text") {
+ thisOverlay = new TextOverlay();
+ thisOverlay->init(_parent);
+ thisOverlay->setProperties(properties);
+ created = true;
+ } else if (type == "cube") {
+ thisOverlay = new Cube3DOverlay();
+ thisOverlay->init(_parent);
+ thisOverlay->setProperties(properties);
+ created = true;
+ is3D = true;
+ } else if (type == "sphere") {
+ thisOverlay = new Sphere3DOverlay();
+ thisOverlay->init(_parent);
+ thisOverlay->setProperties(properties);
+ created = true;
+ is3D = true;
+ } else if (type == "line3d") {
+ thisOverlay = new Line3DOverlay();
+ thisOverlay->init(_parent);
+ thisOverlay->setProperties(properties);
+ created = true;
+ is3D = true;
+ }
+
+ if (created) {
+ thisID = _nextOverlayID;
+ _nextOverlayID++;
+ if (is3D) {
+ _overlays3D[thisID] = thisOverlay;
+ } else {
+ _overlays2D[thisID] = thisOverlay;
+ }
+ }
+
+ return thisID;
+}
+
+// TODO: make multi-threaded safe
+bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
+ Overlay* thisOverlay = NULL;
+ if (_overlays2D.contains(id)) {
+ thisOverlay = _overlays2D[id];
+ } else if (_overlays3D.contains(id)) {
+ thisOverlay = _overlays3D[id];
+ }
+ if (thisOverlay) {
+ thisOverlay->setProperties(properties);
+ return true;
+ }
+ return false;
+}
+
+// TODO: make multi-threaded safe
+void Overlays::deleteOverlay(unsigned int id) {
+ if (_overlays2D.contains(id)) {
+ _overlays2D.erase(_overlays2D.find(id));
+ } else if (_overlays3D.contains(id)) {
+ _overlays3D.erase(_overlays3D.find(id));
+ }
+}
+
+unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
+ QMapIterator i(_overlays2D);
+ i.toBack();
+ while (i.hasPrevious()) {
+ i.previous();
+ unsigned int thisID = i.key();
+ Overlay2D* thisOverlay = static_cast(i.value());
+ if (thisOverlay->getVisible() && thisOverlay->getBounds().contains(point.x, point.y, false)) {
+ return thisID;
+ }
+ }
+ return 0; // not found
+}
+
+
diff --git a/interface/src/ui/Overlays.h b/interface/src/ui/Overlays.h
new file mode 100644
index 0000000000..cfd84fd44b
--- /dev/null
+++ b/interface/src/ui/Overlays.h
@@ -0,0 +1,46 @@
+//
+// Overlays.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Overlays__
+#define __interface__Overlays__
+
+#include
+
+#include "Overlay.h"
+
+class Overlays : public QObject {
+ Q_OBJECT
+public:
+ Overlays();
+ ~Overlays();
+ void init(QGLWidget* parent);
+ void render3D();
+ void render2D();
+
+public slots:
+ /// adds an overlay with the specific properties
+ unsigned int addOverlay(const QString& type, const QScriptValue& properties);
+
+ /// edits an overlay updating only the included properties, will return the identified OverlayID in case of
+ /// successful edit, if the input id is for an unknown overlay this function will have no effect
+ bool editOverlay(unsigned int id, const QScriptValue& properties);
+
+ /// deletes a particle
+ void deleteOverlay(unsigned int id);
+
+ /// returns the top most overlay at the screen point, or 0 if not overlay at that point
+ unsigned int getOverlayAtPoint(const glm::vec2& point);
+
+private:
+ QMap _overlays2D;
+ QMap _overlays3D;
+ static unsigned int _nextOverlayID;
+ QGLWidget* _parent;
+};
+
+
+#endif /* defined(__interface__Overlays__) */
diff --git a/interface/src/ui/Sphere3DOverlay.cpp b/interface/src/ui/Sphere3DOverlay.cpp
new file mode 100644
index 0000000000..7fded5bedb
--- /dev/null
+++ b/interface/src/ui/Sphere3DOverlay.cpp
@@ -0,0 +1,45 @@
+//
+// Sphere3DOverlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+
+#include "Sphere3DOverlay.h"
+
+Sphere3DOverlay::Sphere3DOverlay() {
+}
+
+Sphere3DOverlay::~Sphere3DOverlay() {
+}
+
+void Sphere3DOverlay::render() {
+ if (!_visible) {
+ return; // do nothing if we're not visible
+ }
+
+ const float MAX_COLOR = 255;
+ glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
+
+
+ glDisable(GL_LIGHTING);
+ glPushMatrix();
+ glTranslatef(_position.x + _size * 0.5f,
+ _position.y + _size * 0.5f,
+ _position.z + _size * 0.5f);
+ glLineWidth(_lineWidth);
+ const int slices = 15;
+ if (_isSolid) {
+ glutSolidSphere(_size, slices, slices);
+ } else {
+ glutWireSphere(_size, slices, slices);
+ }
+ glPopMatrix();
+
+}
diff --git a/interface/src/ui/Sphere3DOverlay.h b/interface/src/ui/Sphere3DOverlay.h
new file mode 100644
index 0000000000..58ed0d7776
--- /dev/null
+++ b/interface/src/ui/Sphere3DOverlay.h
@@ -0,0 +1,23 @@
+//
+// Sphere3DOverlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Sphere3DOverlay__
+#define __interface__Sphere3DOverlay__
+
+#include "Volume3DOverlay.h"
+
+class Sphere3DOverlay : public Volume3DOverlay {
+ Q_OBJECT
+
+public:
+ Sphere3DOverlay();
+ ~Sphere3DOverlay();
+ virtual void render();
+};
+
+
+#endif /* defined(__interface__Sphere3DOverlay__) */
diff --git a/interface/src/ui/TextOverlay.cpp b/interface/src/ui/TextOverlay.cpp
new file mode 100644
index 0000000000..edaec6849a
--- /dev/null
+++ b/interface/src/ui/TextOverlay.cpp
@@ -0,0 +1,82 @@
+//
+// TextOverlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+
+#include "TextOverlay.h"
+#include "TextRenderer.h"
+
+TextOverlay::TextOverlay() :
+ _leftMargin(DEFAULT_MARGIN),
+ _topMargin(DEFAULT_MARGIN)
+{
+}
+
+TextOverlay::~TextOverlay() {
+}
+
+void TextOverlay::render() {
+ if (!_visible) {
+ return; // do nothing if we're not visible
+ }
+
+ const float MAX_COLOR = 255;
+ glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
+
+ glBegin(GL_QUADS);
+ glVertex2f(_bounds.left(), _bounds.top());
+ glVertex2f(_bounds.right(), _bounds.top());
+ glVertex2f(_bounds.right(), _bounds.bottom());
+ glVertex2f(_bounds.left(), _bounds.bottom());
+ glEnd();
+
+ //TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
+ // EffectType effect = NO_EFFECT, int effectThickness = 1);
+
+ TextRenderer textRenderer(SANS_FONT_FAMILY, 11, 50);
+ const int leftAdjust = -1; // required to make text render relative to left edge of bounds
+ const int topAdjust = -2; // required to make text render relative to top edge of bounds
+ int x = _bounds.left() + _leftMargin + leftAdjust;
+ int y = _bounds.top() + _topMargin + topAdjust;
+
+ glColor3f(1.0f, 1.0f, 1.0f);
+ QStringList lines = _text.split("\n");
+ int lineOffset = 0;
+ foreach(QString thisLine, lines) {
+ if (lineOffset == 0) {
+ lineOffset = textRenderer.calculateHeight(qPrintable(thisLine));
+ }
+ lineOffset += textRenderer.draw(x, y + lineOffset, qPrintable(thisLine));
+
+ const int lineGap = 2;
+ lineOffset += lineGap;
+ }
+
+}
+
+void TextOverlay::setProperties(const QScriptValue& properties) {
+ Overlay2D::setProperties(properties);
+
+ QScriptValue text = properties.property("text");
+ if (text.isValid()) {
+ setText(text.toVariant().toString());
+ }
+
+ if (properties.property("leftMargin").isValid()) {
+ setLeftMargin(properties.property("leftMargin").toVariant().toInt());
+ }
+
+ if (properties.property("topMargin").isValid()) {
+ setTopMargin(properties.property("topMargin").toVariant().toInt());
+ }
+}
+
+
diff --git a/interface/src/ui/TextOverlay.h b/interface/src/ui/TextOverlay.h
new file mode 100644
index 0000000000..d565aeb70d
--- /dev/null
+++ b/interface/src/ui/TextOverlay.h
@@ -0,0 +1,59 @@
+//
+// TextOverlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__TextOverlay__
+#define __interface__TextOverlay__
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "Overlay.h"
+#include "Overlay2D.h"
+
+const int DEFAULT_MARGIN = 10;
+
+class TextOverlay : public Overlay2D {
+ Q_OBJECT
+
+public:
+ TextOverlay();
+ ~TextOverlay();
+ virtual void render();
+
+ // getters
+ const QString& getText() const { return _text; }
+ int getLeftMargin() const { return _leftMargin; }
+ int getTopMargin() const { return _topMargin; }
+
+ // setters
+ void setText(const QString& text) { _text = text; }
+ void setLeftMargin(int margin) { _leftMargin = margin; }
+ void setTopMargin(int margin) { _topMargin = margin; }
+
+ virtual void setProperties(const QScriptValue& properties);
+
+private:
+
+ QString _text;
+ int _leftMargin;
+ int _topMargin;
+
+};
+
+
+#endif /* defined(__interface__TextOverlay__) */
diff --git a/interface/src/ui/TextRenderer.cpp b/interface/src/ui/TextRenderer.cpp
index 65056799e2..cacd730fd6 100644
--- a/interface/src/ui/TextRenderer.cpp
+++ b/interface/src/ui/TextRenderer.cpp
@@ -8,6 +8,8 @@
#include
#include
#include
+#include
+#include
#include "InterfaceConfig.h"
#include "TextRenderer.h"
@@ -30,10 +32,25 @@ TextRenderer::~TextRenderer() {
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
}
-void TextRenderer::draw(int x, int y, const char* str) {
+int TextRenderer::calculateHeight(const char* str) {
+ int maxHeight = 0;
+ for (const char* ch = str; *ch != 0; ch++) {
+ const Glyph& glyph = getGlyph(*ch);
+ if (glyph.textureID() == 0) {
+ continue;
+ }
+
+ if (glyph.bounds().height() > maxHeight) {
+ maxHeight = glyph.bounds().height();
+ }
+ }
+ return maxHeight;
+}
+int TextRenderer::draw(int x, int y, const char* str) {
glEnable(GL_TEXTURE_2D);
+ int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
@@ -41,19 +58,23 @@ void TextRenderer::draw(int x, int y, const char* str) {
continue;
}
+ if (glyph.bounds().height() > maxHeight) {
+ maxHeight = glyph.bounds().height();
+ }
+
glBindTexture(GL_TEXTURE_2D, glyph.textureID());
-
+
int left = x + glyph.bounds().x();
int right = x + glyph.bounds().x() + glyph.bounds().width();
int bottom = y + glyph.bounds().y();
int top = y + glyph.bounds().y() + glyph.bounds().height();
-
+
float scale = 1.0 / IMAGE_SIZE;
float ls = glyph.location().x() * scale;
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
float bt = glyph.location().y() * scale;
float tt = (glyph.location().y() + glyph.bounds().height()) * scale;
-
+
glBegin(GL_QUADS);
glTexCoord2f(ls, bt);
glVertex2f(left, bottom);
@@ -64,12 +85,13 @@ void TextRenderer::draw(int x, int y, const char* str) {
glTexCoord2f(ls, tt);
glVertex2f(left, top);
glEnd();
-
+
x += glyph.width();
}
-
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
+
+ return maxHeight;
}
int TextRenderer::computeWidth(char ch)
diff --git a/interface/src/ui/TextRenderer.h b/interface/src/ui/TextRenderer.h
index ff484066d8..d6c24c1ce8 100644
--- a/interface/src/ui/TextRenderer.h
+++ b/interface/src/ui/TextRenderer.h
@@ -20,6 +20,16 @@
// a special "character" that renders as a solid block
const char SOLID_BLOCK_CHAR = 127;
+// the standard sans serif font family
+#define SANS_FONT_FAMILY "Helvetica"
+
+// the standard mono font family
+#define MONO_FONT_FAMILY "Courier"
+
+// the Inconsolata font family
+#define INCONSOLATA_FONT_FAMILY "Inconsolata"
+
+
class Glyph;
class TextRenderer {
@@ -33,7 +43,11 @@ public:
const QFontMetrics& metrics() const { return _metrics; }
- void draw(int x, int y, const char* str);
+ // returns the height of the tallest character
+ int calculateHeight(const char* str);
+
+ // also returns the height of the tallest character
+ int draw(int x, int y, const char* str);
int computeWidth(char ch);
int computeWidth(const char* str);
diff --git a/interface/src/ui/Volume3DOverlay.cpp b/interface/src/ui/Volume3DOverlay.cpp
new file mode 100644
index 0000000000..dbc1582cc5
--- /dev/null
+++ b/interface/src/ui/Volume3DOverlay.cpp
@@ -0,0 +1,47 @@
+//
+// Volume3DOverlay.cpp
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+
+#include "Volume3DOverlay.h"
+
+const float DEFAULT_SIZE = 1.0f;
+const bool DEFAULT_IS_SOLID = false;
+
+Volume3DOverlay::Volume3DOverlay() :
+ _size(DEFAULT_SIZE),
+ _isSolid(DEFAULT_IS_SOLID)
+{
+}
+
+Volume3DOverlay::~Volume3DOverlay() {
+}
+
+void Volume3DOverlay::setProperties(const QScriptValue& properties) {
+ Base3DOverlay::setProperties(properties);
+
+ if (properties.property("size").isValid()) {
+ setSize(properties.property("size").toVariant().toFloat());
+ }
+
+ if (properties.property("isSolid").isValid()) {
+ setIsSolid(properties.property("isSolid").toVariant().toBool());
+ }
+ if (properties.property("isWire").isValid()) {
+ setIsSolid(!properties.property("isWire").toVariant().toBool());
+ }
+ if (properties.property("solid").isValid()) {
+ setIsSolid(properties.property("solid").toVariant().toBool());
+ }
+ if (properties.property("wire").isValid()) {
+ setIsSolid(!properties.property("wire").toVariant().toBool());
+ }
+}
diff --git a/interface/src/ui/Volume3DOverlay.h b/interface/src/ui/Volume3DOverlay.h
new file mode 100644
index 0000000000..8badbf2c33
--- /dev/null
+++ b/interface/src/ui/Volume3DOverlay.h
@@ -0,0 +1,42 @@
+//
+// Volume3DOverlay.h
+// interface
+//
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Volume3DOverlay__
+#define __interface__Volume3DOverlay__
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+#include
+
+#include "Base3DOverlay.h"
+
+class Volume3DOverlay : public Base3DOverlay {
+ Q_OBJECT
+
+public:
+ Volume3DOverlay();
+ ~Volume3DOverlay();
+
+ // getters
+ float getSize() const { return _size; }
+ bool getIsSolid() const { return _isSolid; }
+
+ // setters
+ void setSize(float size) { _size = size; }
+ void setIsSolid(bool isSolid) { _isSolid = isSolid; }
+
+ virtual void setProperties(const QScriptValue& properties);
+
+protected:
+ float _size;
+ bool _isSolid;
+};
+
+
+#endif /* defined(__interface__Volume3DOverlay__) */
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 46d92c0f2e..6492d9b7ad 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -52,8 +52,8 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
-const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx");
-const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx");
+const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
+const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
enum KeyState {
NO_KEY_DOWN = 0,
@@ -67,6 +67,7 @@ class AvatarData : public NodeData {
Q_OBJECT
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)
+ Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale)
Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition)
Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw)
Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch)
diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp
index 72ada7d421..82485691c5 100644
--- a/libraries/avatars/src/AvatarHashMap.cpp
+++ b/libraries/avatars/src/AvatarHashMap.cpp
@@ -2,7 +2,7 @@
// AvatarHashMap.cpp
// hifi
//
-// Created by Stephen AndrewMeadows on 1/28/2014.
+// Created by AndrewMeadows on 1/28/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp
index f863d6b592..62e8276bd3 100644
--- a/libraries/avatars/src/HeadData.cpp
+++ b/libraries/avatars/src/HeadData.cpp
@@ -45,10 +45,3 @@ void HeadData::addLean(float sideways, float 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;
-}
-
diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h
index fde684bbf1..0f096059c0 100644
--- a/libraries/avatars/src/HeadData.h
+++ b/libraries/avatars/src/HeadData.h
@@ -58,13 +58,6 @@ public:
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
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:
float _yaw;
diff --git a/libraries/octree/src/OctreeScriptingInterface.cpp b/libraries/octree/src/OctreeScriptingInterface.cpp
index 553ab961df..1ed82564b6 100644
--- a/libraries/octree/src/OctreeScriptingInterface.cpp
+++ b/libraries/octree/src/OctreeScriptingInterface.cpp
@@ -11,7 +11,12 @@
#include "OctreeScriptingInterface.h"
OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packetSender,
- JurisdictionListener* jurisdictionListener)
+ JurisdictionListener* jurisdictionListener) :
+ _packetSender(NULL),
+ _jurisdictionListener(NULL),
+ _managedPacketSender(false),
+ _managedJurisdictionListener(false),
+ _initialized(false)
{
setPacketSender(packetSender);
setJurisdictionListener(jurisdictionListener);
@@ -45,6 +50,9 @@ void OctreeScriptingInterface::setJurisdictionListener(JurisdictionListener* jur
}
void OctreeScriptingInterface::init() {
+ if (_initialized) {
+ return;
+ }
if (_jurisdictionListener) {
_managedJurisdictionListener = false;
} else {
@@ -64,5 +72,5 @@ void OctreeScriptingInterface::init() {
if (QCoreApplication::instance()) {
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(cleanupManagedObjects()));
}
-
+ _initialized = true;
}
diff --git a/libraries/octree/src/OctreeScriptingInterface.h b/libraries/octree/src/OctreeScriptingInterface.h
index 34eddd8bed..3c832cbae8 100644
--- a/libraries/octree/src/OctreeScriptingInterface.h
+++ b/libraries/octree/src/OctreeScriptingInterface.h
@@ -93,6 +93,7 @@ protected:
JurisdictionListener* _jurisdictionListener;
bool _managedPacketSender;
bool _managedJurisdictionListener;
+ bool _initialized;
};
#endif /* defined(__hifi__OctreeScriptingInterface__) */
diff --git a/libraries/script-engine/src/AbstractControllerScriptingInterface.h b/libraries/script-engine/src/AbstractControllerScriptingInterface.h
index 4fad5f6edc..d9878d0b71 100644
--- a/libraries/script-engine/src/AbstractControllerScriptingInterface.h
+++ b/libraries/script-engine/src/AbstractControllerScriptingInterface.h
@@ -12,6 +12,7 @@
#include
#include
+#include
#include "EventTypes.h"
@@ -37,6 +38,7 @@ public slots:
virtual glm::vec3 getSpatialControlPosition(int controlIndex) const = 0;
virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const = 0;
virtual glm::vec3 getSpatialControlNormal(int controlIndex) const = 0;
+ virtual glm::quat getSpatialControlRawRotation(int controlIndex) const = 0;
virtual void captureKeyEvents(const KeyEvent& event) = 0;
virtual void releaseKeyEvents(const KeyEvent& event) = 0;
diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp
index a197d59aeb..2f1c39f9e3 100644
--- a/libraries/script-engine/src/Quat.cpp
+++ b/libraries/script-engine/src/Quat.cpp
@@ -9,7 +9,10 @@
//
//
+#include
+
#include
+#include
#include "Quat.h"
glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) {
@@ -24,6 +27,11 @@ glm::quat Quat::fromPitchYawRoll(float pitch, float yaw, float roll) {
return glm::quat(glm::radians(glm::vec3(pitch, yaw, roll)));
}
+glm::quat Quat::inverse(const glm::quat& q) {
+ return glm::inverse(q);
+}
+
+
glm::vec3 Quat::getFront(const glm::quat& orientation) {
return orientation * IDENTITY_FRONT;
}
@@ -35,3 +43,12 @@ glm::vec3 Quat::getRight(const glm::quat& orientation) {
glm::vec3 Quat::getUp(const glm::quat& orientation) {
return orientation * IDENTITY_UP;
}
+
+glm::vec3 Quat::safeEulerAngles(const glm::quat& orientation) {
+ return ::safeEulerAngles(orientation);
+}
+
+glm::quat Quat::angleAxis(float angle, const glm::vec3& v) {
+ return glm::angleAxis(angle, v);
+}
+
diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h
index 72fec6d6dc..867069d6d6 100644
--- a/libraries/script-engine/src/Quat.h
+++ b/libraries/script-engine/src/Quat.h
@@ -23,11 +23,12 @@ public slots:
glm::quat multiply(const glm::quat& q1, const glm::quat& q2);
glm::quat fromVec3(const glm::vec3& vec3);
glm::quat fromPitchYawRoll(float pitch, float yaw, float roll);
+ glm::quat inverse(const glm::quat& q);
glm::vec3 getFront(const glm::quat& orientation);
glm::vec3 getRight(const glm::quat& orientation);
glm::vec3 getUp(const glm::quat& orientation);
+ glm::vec3 safeEulerAngles(const glm::quat& orientation);
+ glm::quat angleAxis(float angle, const glm::vec3& v);
};
-
-
#endif /* defined(__hifi__Quat__) */
diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp
index 87b1b510a4..1ed3ae6915 100644
--- a/libraries/script-engine/src/Vec3.cpp
+++ b/libraries/script-engine/src/Vec3.cpp
@@ -19,6 +19,16 @@ glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) {
return v1 * f;
}
+glm::vec3 Vec3::multiplyQbyV(const glm::quat& q, const glm::vec3& v) {
+ return q * v;
+}
+
glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& 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) {
+ return glm::length(v);
+}
diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h
index 1cc44f3061..20ad3f7eaa 100644
--- a/libraries/script-engine/src/Vec3.h
+++ b/libraries/script-engine/src/Vec3.h
@@ -23,7 +23,10 @@ class Vec3 : public QObject {
public slots:
glm::vec3 multiply(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 multiply(const glm::vec3& v1, float f);
+ glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
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);
};
diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h
index fffb095021..a7a10b97b8 100644
--- a/libraries/shared/src/PerfStat.h
+++ b/libraries/shared/src/PerfStat.h
@@ -45,6 +45,8 @@ public:
_alwaysDisplay(alwaysDisplay),
_runningTotal(runningTotal),
_totalCalls(totalCalls) { }
+
+ quint64 elapsed() const { return (usecTimestampNow() - _start); };
~PerformanceWarning();