diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js
index 923a01a8a9..9d586c8436 100644
--- a/domain-server/resources/web/js/settings.js
+++ b/domain-server/resources/web/js/settings.js
@@ -152,7 +152,6 @@ $(document).ready(function(){
})
$('#settings-form').on('change', 'select', function(){
- console.log("Changed" + $(this))
$("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change()
})
@@ -474,21 +473,24 @@ function deleteTableRow(delete_glyphicon) {
if (!isArray) {
// this is a hash row, so we empty it but leave the hidden input blank so it is cleared when we save
row.empty()
- row.html("");
- } else if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
- updateDataChangedForSiblingRows(row)
-
- // this isn't the last row - we can just remove it
- row.remove()
+ row.html("");
} else {
- // this is the last row, we can't remove it completely since we need to post an empty array
- row.empty()
+ if (table.find('.' + Settings.DATA_ROW_CLASS).length) {
+ updateDataChangedForSiblingRows(row)
- row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
- row.addClass('empty-array-row')
+ // this isn't the last row - we can just remove it
+ row.remove()
+ } else {
+ // this is the last row, we can't remove it completely since we need to post an empty array
+ row.empty()
- row.html("");
+ row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
+ row.addClass('empty-array-row')
+
+ row.html("");
+ }
}
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
diff --git a/examples/gamepad.js b/examples/gamepad.js
new file mode 100644
index 0000000000..4ec0309511
--- /dev/null
+++ b/examples/gamepad.js
@@ -0,0 +1,280 @@
+//
+// controller.js
+// examples
+//
+// Created by Ryan Huffman on 10/9/14.
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// TODO Update to work with any controller that is plugged in.
+var CONTROLLER_NAMES = [
+ "Wireless 360 Controller",
+ "Controller (XBOX 360 For Windows)",
+ "Controller", // Wired 360 controller
+]
+
+for (var i = 0; i < CONTROLLER_NAMES.length; i++) {
+ gamepad = Joysticks.joystickWithName(CONTROLLER_NAMES[i]);
+ if (gamepad) {
+ print("Found controller: " + CONTROLLER_NAMES[i]);
+ break;
+ }
+}
+
+if (!gamepad) {
+ print("No gamepad found.");
+}
+
+// Controller axis/button mappings
+var GAMEPAD = {
+ AXES: {
+ LEFT_JOYSTICK_X: 0,
+ LEFT_JOYSTICK_Y: 1,
+
+ RIGHT_JOYSTICK_X: 2,
+ RIGHT_JOYSTICK_Y: 3,
+
+ LEFT_TRIGGER: 4,
+ RIGHT_TRIGGER: 5,
+ },
+ BUTTONS: {
+ DPAD_UP: 0,
+ DPAD_DOWN: 1,
+ DPAD_LEFT: 2,
+ DPAD_RIGHT: 3,
+
+ LEFT_JOYSTICK: 6,
+ RIGHT_JOYSTICK: 7,
+
+ LEFT_BUMPER: 8,
+ RIGHT_BUMPER: 9,
+
+ // Face buttons, ABXY on an XBOX controller
+ FACE_BOTTOM: 11,
+ FACE_RIGHT: 12,
+ FACE_LEFT: 13,
+ FACE_TOP: 14,
+ }
+}
+
+// Button/axis mappings
+var AXIS_STRAFE = GAMEPAD.AXES.LEFT_JOYSTICK_X;
+var AXIS_FORWARD = GAMEPAD.AXES.LEFT_JOYSTICK_Y;
+var AXIS_ROTATE = GAMEPAD.AXES.RIGHT_JOYSTICK_X;
+
+var BUTTON_TURN_AROUND = GAMEPAD.BUTTONS.RIGHT_JOYSTICK;
+
+var BUTTON_FLY_UP = GAMEPAD.BUTTONS.RIGHT_BUMPER;
+var BUTTON_FLY_DOWN = GAMEPAD.BUTTONS.LEFT_BUMPER
+var BUTTON_WARP = GAMEPAD.BUTTONS.FACE_BOTTOM;
+
+var BUTTON_WARP_FORWARD = GAMEPAD.BUTTONS.DPAD_UP;
+var BUTTON_WARP_BACKWARD = GAMEPAD.BUTTONS.DPAD_DOWN;
+var BUTTON_WARP_LEFT = GAMEPAD.BUTTONS.DPAD_LEFT;
+var BUTTON_WARP_RIGHT = GAMEPAD.BUTTONS.DPAD_RIGHT;
+
+// Distance in meters to warp via BUTTON_WARP_*
+var WARP_DISTANCE = 1;
+
+// Walk speed in m/s
+var MOVE_SPEED = 2;
+
+// Amount to rotate in radians
+var ROTATE_INCREMENT = Math.PI / 8;
+
+// Pick from above where we want to warp
+var WARP_PICK_OFFSET = { x: 0, y: 10, z: 0 };
+
+// When warping, the warp position will snap to a target below the current warp position.
+// This is the max distance it will snap to.
+var WARP_PICK_MAX_DISTANCE = 100;
+
+var flyDownButtonState = false;
+var flyUpButtonState = false;
+
+// Current move direction, axis aligned - that is, looking down and moving forward
+// will not move you into the ground, but instead will keep you on the horizontal plane.
+var moveDirection = { x: 0, y: 0, z: 0 };
+
+var warpActive = false;
+var warpPosition = { x: 0, y: 0, z: 0 };
+
+var WARP_SPHERE_SIZE = 1;
+var warpSphere = Overlays.addOverlay("sphere", {
+ position: { x: 0, y: 0, z: 0 },
+ size: WARP_SPHERE_SIZE,
+ color: { red: 0, green: 255, blue: 0 },
+ alpha: 1.0,
+ solid: true,
+ visible: false,
+});
+
+var WARP_LINE_HEIGHT = 10;
+var warpLine = Overlays.addOverlay("line3d", {
+ position: { x: 0, y: 0, z:0 },
+ end: { x: 0, y: 0, z: 0 },
+ color: { red: 0, green: 255, blue: 255},
+ alpha: 1,
+ lineWidth: 5,
+ visible: false,
+});
+
+function copyVec3(vec) {
+ return { x: vec.x, y: vec.y, z: vec.z };
+}
+
+function activateWarp() {
+ if (warpActive) return;
+ warpActive = true;
+
+ updateWarp();
+}
+
+function updateWarp() {
+ if (!warpActive) return;
+
+ var look = Quat.getFront(Camera.getOrientation());
+ var pitch = Math.asin(look.y);
+
+ // Get relative to looking straight down
+ pitch += Math.PI / 2;
+
+ // Scale up
+ pitch *= 2;
+ var distance = pitch * pitch * pitch;
+
+ var warpDirection = Vec3.normalize({ x: look.x, y: 0, z: look.z });
+ warpPosition = Vec3.multiply(warpDirection, distance);
+ warpPosition = Vec3.sum(MyAvatar.position, warpPosition);
+
+ var pickRay = {
+ origin: Vec3.sum(warpPosition, WARP_PICK_OFFSET),
+ direction: { x: 0, y: -1, z: 0 }
+ };
+
+ var intersection = Voxels.findRayIntersection(pickRay);
+
+ if (intersection.intersects && intersection.distance < WARP_PICK_MAX_DISTANCE) {
+ // Warp 1 meter above the object - this is an approximation
+ // TODO Get the actual offset to the Avatar's feet and plant them to
+ // the object.
+ warpPosition = Vec3.sum(intersection.intersection, { x: 0, y: 1, z:0 });
+ }
+
+ // Adjust overlays to match warp position
+ Overlays.editOverlay(warpSphere, {
+ position: warpPosition,
+ visible: true,
+ });
+ Overlays.editOverlay(warpLine, {
+ position: warpPosition,
+ end: Vec3.sum(warpPosition, { x: 0, y: WARP_LINE_HEIGHT, z: 0 }),
+ visible: true,
+ });
+}
+
+function finishWarp() {
+ if (!warpActive) return;
+ warpActive = false;
+ Overlays.editOverlay(warpSphere, {
+ visible: false,
+ });
+ Overlays.editOverlay(warpLine, {
+ visible: false,
+ });
+ MyAvatar.position = warpPosition;
+}
+
+function reportAxisValue(axis, newValue, oldValue) {
+ if (Math.abs(oldValue) < 0.2) oldValue = 0;
+ if (Math.abs(newValue) < 0.2) newValue = 0;
+
+ if (axis == AXIS_FORWARD) {
+ moveDirection.z = newValue;
+ } else if (axis == AXIS_STRAFE) {
+ moveDirection.x = newValue;
+ } else if (axis == AXIS_ROTATE) {
+ if (oldValue == 0 && newValue != 0) {
+ var rotateRadians = newValue > 0 ? -ROTATE_INCREMENT : ROTATE_INCREMENT;
+ var orientation = MyAvatar.orientation;
+ orientation = Quat.multiply(Quat.fromPitchYawRollRadians(0, rotateRadians, 0), orientation) ;
+ MyAvatar.orientation = orientation;
+ }
+ }
+}
+
+function reportButtonValue(button, newValue, oldValue) {
+ if (button == BUTTON_FLY_DOWN) {
+ flyDownButtonState = newValue;
+ } else if (button == BUTTON_FLY_UP) {
+ flyUpButtonState = newValue;
+ } else if (button == BUTTON_WARP) {
+ if (newValue) {
+ activateWarp();
+ } else {
+ finishWarp();
+ }
+ } else if (button == BUTTON_TURN_AROUND) {
+ if (newValue) {
+ MyAvatar.orientation = Quat.multiply(
+ Quat.fromPitchYawRollRadians(0, Math.PI, 0), MyAvatar.orientation);
+ }
+ } else if (newValue) {
+ var direction = null;
+
+ if (button == BUTTON_WARP_FORWARD) {
+ direction = Quat.getFront(Camera.getOrientation());
+ } else if (button == BUTTON_WARP_BACKWARD) {
+ direction = Quat.getFront(Camera.getOrientation());
+ direction = Vec3.multiply(-1, direction);
+ } else if (button == BUTTON_WARP_LEFT) {
+ direction = Quat.getRight(Camera.getOrientation());
+ direction = Vec3.multiply(-1, direction);
+ } else if (button == BUTTON_WARP_RIGHT) {
+ direction = Quat.getRight(Camera.getOrientation());
+ }
+
+ if (direction) {
+ direction.y = 0;
+ direction = Vec3.multiply(Vec3.normalize(direction), WARP_DISTANCE);
+ MyAvatar.position = Vec3.sum(MyAvatar.position, direction);
+ }
+ }
+
+ if (flyUpButtonState && !flyDownButtonState) {
+ moveDirection.y = 1;
+ } else if (!flyUpButtonState && flyDownButtonState) {
+ moveDirection.y = -1;
+ } else {
+ moveDirection.y = 0;
+ }
+}
+
+function update(dt) {
+ var velocity = { x: 0, y: 0, z: 0 };
+ var move = copyVec3(moveDirection);
+ move.y = 0;
+ if (Vec3.length(move) > 0) {
+ velocity = Vec3.multiplyQbyV(Camera.getOrientation(), move);
+ velocity.y = 0;
+ velocity = Vec3.multiply(Vec3.normalize(velocity), MOVE_SPEED);
+ }
+
+ if (moveDirection.y != 0) {
+ velocity.y = moveDirection.y * MOVE_SPEED;
+ }
+
+ MyAvatar.setVelocity(velocity);
+
+ updateWarp();
+}
+
+if (gamepad) {
+ gamepad.axisValueChanged.connect(reportAxisValue);
+ gamepad.buttonStateChanged.connect(reportButtonValue);
+
+ Script.update.connect(update);
+}
diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js
index fbac30e796..becc8c86b2 100644
--- a/examples/libraries/entityPropertyDialogBox.js
+++ b/examples/libraries/entityPropertyDialogBox.js
@@ -244,7 +244,7 @@ EntityPropertyDialogBox = (function () {
properties.color.blue = array[index++].value;
}
Entities.editEntity(editModelID, properties);
- selectionDisplay.highlightSelectable(editModelID, propeties);
+ selectionDisplay.select(editModelID, false);
}
modelSelected = false;
});
diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js
index d9cf2c54fd..b00876c969 100644
--- a/examples/libraries/entitySelectionTool.js
+++ b/examples/libraries/entitySelectionTool.js
@@ -31,10 +31,33 @@ SelectionDisplay = (function () {
var handleHoverColor = { red: 224, green: 67, blue: 36 };
var handleHoverAlpha = 1.0;
+ var rotateOverlayTargetSize = 10000; // really big target
+ var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool
+ var innerRadius;
+ var outerRadius;
+ var yawHandleRotation;
+ var pitchHandleRotation;
+ var rollHandleRotation;
+ var yawCenter;
+ var pitchCenter;
+ var rollCenter;
+ var yawZero;
+ var pitchZero;
+ var rollZero;
+ var yawNormal;
+ var pitchNormal;
+ var rollNormal;
+ var rotationNormal;
+
+ var originalRotation;
+ var originalPitch;
+ var originalYaw;
+ var originalRoll;
+
+
var rotateHandleColor = { red: 0, green: 0, blue: 0 };
var rotateHandleAlpha = 0.7;
-
var grabberSizeCorner = 0.025;
var grabberSizeEdge = 0.015;
var grabberSizeFace = 0.025;
@@ -151,7 +174,8 @@ SelectionDisplay = (function () {
alpha: 0.5,
solid: true,
visible: false,
- rotation: baseOverlayRotation
+ rotation: baseOverlayRotation,
+ ignoreRayIntersection: true, // always ignore this
});
var yawOverlayAngles = { x: 90, y: 0, z: 0 };
@@ -161,6 +185,34 @@ SelectionDisplay = (function () {
var rollOverlayAngles = { x: 0, y: 180, z: 0 };
var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles);
+ var rotateZeroOverlay = Overlays.addOverlay("line3d", {
+ visible: false,
+ lineWidth: 2.0,
+ start: { x: 0, y: 0, z: 0 },
+ end: { x: 0, y: 0, z: 0 },
+ color: { red: 255, green: 0, blue: 0 },
+ ignoreRayIntersection: true, // always ignore this
+ });
+
+ var rotateCurrentOverlay = Overlays.addOverlay("line3d", {
+ visible: false,
+ lineWidth: 2.0,
+ start: { x: 0, y: 0, z: 0 },
+ end: { x: 0, y: 0, z: 0 },
+ color: { red: 0, green: 0, blue: 255 },
+ ignoreRayIntersection: true, // always ignore this
+ });
+
+
+ var rotateOverlayTarget = Overlays.addOverlay("circle3d", {
+ position: { x:0, y: 0, z: 0},
+ size: rotateOverlayTargetSize,
+ color: { red: 0, green: 0, blue: 0 },
+ alpha: 0.0,
+ solid: true,
+ visible: false,
+ rotation: yawOverlayRotation,
+ });
var rotateOverlayInner = Overlays.addOverlay("circle3d", {
position: { x:0, y: 0, z: 0},
@@ -171,12 +223,13 @@ SelectionDisplay = (function () {
visible: false,
rotation: yawOverlayRotation,
hasTickMarks: true,
- majorTickMarksAngle: 12.5,
+ majorTickMarksAngle: innerSnapAngle,
minorTickMarksAngle: 0,
majorTickMarksLength: -0.25,
minorTickMarksLength: 0,
majorTickMarksColor: { red: 0, green: 0, blue: 0 },
minorTickMarksColor: { red: 0, green: 0, blue: 0 },
+ ignoreRayIntersection: true, // always ignore this
});
var rotateOverlayOuter = Overlays.addOverlay("circle3d", {
@@ -195,6 +248,7 @@ SelectionDisplay = (function () {
minorTickMarksLength: 0.1,
majorTickMarksColor: { red: 0, green: 0, blue: 0 },
minorTickMarksColor: { red: 0, green: 0, blue: 0 },
+ ignoreRayIntersection: true, // always ignore this
});
var rotateOverlayCurrent = Overlays.addOverlay("circle3d", {
@@ -205,10 +259,11 @@ SelectionDisplay = (function () {
solid: true,
visible: false,
rotation: yawOverlayRotation,
+ ignoreRayIntersection: true, // always ignore this
});
var yawHandle = Overlays.addOverlay("billboard", {
- url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png",
+ url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png",
position: { x:0, y: 0, z: 0},
color: rotateHandleColor,
alpha: rotateHandleAlpha,
@@ -220,7 +275,7 @@ SelectionDisplay = (function () {
var pitchHandle = Overlays.addOverlay("billboard", {
- url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png",
+ url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png",
position: { x:0, y: 0, z: 0},
color: rotateHandleColor,
alpha: rotateHandleAlpha,
@@ -232,7 +287,7 @@ SelectionDisplay = (function () {
var rollHandle = Overlays.addOverlay("billboard", {
- url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png",
+ url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png",
position: { x:0, y: 0, z: 0},
color: rotateHandleColor,
alpha: rotateHandleAlpha,
@@ -279,11 +334,14 @@ SelectionDisplay = (function () {
overlayNames[pitchHandle] = "pitchHandle";
overlayNames[rollHandle] = "rollHandle";
+ overlayNames[rotateOverlayTarget] = "rotateOverlayTarget";
overlayNames[rotateOverlayInner] = "rotateOverlayInner";
overlayNames[rotateOverlayOuter] = "rotateOverlayOuter";
overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent";
-
+ overlayNames[rotateZeroOverlay] = "rotateZeroOverlay";
+ overlayNames[rotateCurrentOverlay] = "rotateCurrentOverlay";
+
that.cleanup = function () {
Overlays.deleteOverlay(highlightBox);
Overlays.deleteOverlay(selectionBox);
@@ -322,10 +380,15 @@ SelectionDisplay = (function () {
Overlays.deleteOverlay(pitchHandle);
Overlays.deleteOverlay(rollHandle);
+ Overlays.deleteOverlay(rotateOverlayTarget);
Overlays.deleteOverlay(rotateOverlayInner);
Overlays.deleteOverlay(rotateOverlayOuter);
Overlays.deleteOverlay(rotateOverlayCurrent);
+ Overlays.deleteOverlay(rotateZeroOverlay);
+ Overlays.deleteOverlay(rotateCurrentOverlay);
+
+
};
that.highlightSelectable = function(entityID) {
@@ -377,8 +440,8 @@ SelectionDisplay = (function () {
var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1;
var halfDimensions = Vec3.multiply(properties.dimensions, 0.5);
- var innerRadius = diagonal;
- var outerRadius = diagonal * 1.15;
+ innerRadius = diagonal;
+ outerRadius = diagonal * 1.15;
var innerActive = false;
var innerAlpha = 0.2;
var outerAlpha = 0.2;
@@ -391,31 +454,33 @@ SelectionDisplay = (function () {
var rotateHandleOffset = 0.05;
var grabberMoveUpOffset = 0.1;
- var left = properties.position.x - halfDimensions.x;
- var right = properties.position.x + halfDimensions.x;
- var bottom = properties.position.y - halfDimensions.y;
- var top = properties.position.y + halfDimensions.y;
- var near = properties.position.z - halfDimensions.z;
- var far = properties.position.z + halfDimensions.z;
- var center = { x: properties.position.x, y: properties.position.y, z: properties.position.z };
+ var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF;
- var BLN = { x: left, y: bottom, z: near };
- var BRN = { x: right, y: bottom, z: near };
- var BLF = { x: left, y: bottom, z: far };
- var BRF = { x: right, y: bottom, z: far };
- var TLN = { x: left, y: top, z: near };
- var TRN = { x: right, y: top, z: near };
- var TLF = { x: left, y: top, z: far };
- var TRF = { x: right, y: top, z: far };
+ objectCenter = { x: properties.position.x, y: properties.position.y, z: properties.position.z };
+
+ top = properties.boundingBox.tfl.y;
+ far = properties.boundingBox.tfl.z;
+ left = properties.boundingBox.tfl.x;
+
+ bottom = properties.boundingBox.brn.y;
+ right = properties.boundingBox.brn.x;
+ near = properties.boundingBox.brn.z;
+
+ boundsCenter = { x: properties.boundingBox.center.x, y: properties.boundingBox.center.y, z: properties.boundingBox.center.z };
+
+ BLN = { x: left, y: bottom, z: near };
+ BRN = { x: right, y: bottom, z: near };
+ BLF = { x: left, y: bottom, z: far };
+ BRF = { x: right, y: bottom, z: far };
+ TLN = { x: left, y: top, z: near };
+ TRN = { x: right, y: top, z: near };
+ TLF = { x: left, y: top, z: far };
+ TRF = { x: right, y: top, z: far };
var yawCorner;
var pitchCorner;
var rollCorner;
- var yawHandleRotation;
- var pitchHandleRotation;
- var rollHandleRotation;
-
// determine which bottom corner we are closest to
/*------------------------------
example:
@@ -429,124 +494,189 @@ SelectionDisplay = (function () {
------------------------------*/
- if (MyAvatar.position.x > center.x) {
+ if (MyAvatar.position.x > objectCenter.x) {
// must be BRF or BRN
- if (MyAvatar.position.z < center.z) {
- yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 });
- pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 });
- rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 180 });
+ if (MyAvatar.position.z < objectCenter.z) {
- yawCorner = { x: right + rotateHandleOffset,
+ yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 90, z: 0 });
+ pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 0 });
+ rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 });
+
+ yawNormal = { x: 0, y: 1, z: 0 };
+ pitchNormal = { x: 1, y: 0, z: 0 };
+ rollNormal = { x: 0, y: 0, z: 1 };
+
+ yawCorner = { x: left + rotateHandleOffset,
y: bottom - rotateHandleOffset,
z: near - rotateHandleOffset };
- pitchCorner = { x: right + rotateHandleOffset,
- y: top + rotateHandleOffset,
- z: far + rotateHandleOffset };
-
- rollCorner = { x: left - rotateHandleOffset,
+ pitchCorner = { x: right - rotateHandleOffset,
y: top + rotateHandleOffset,
z: near - rotateHandleOffset};
+ rollCorner = { x: left + rotateHandleOffset,
+ y: top + rotateHandleOffset,
+ z: far + rotateHandleOffset };
+
+ yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z };
+ pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z};
+ rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far };
+
+
+ Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-south.png" });
+ Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-south.png" });
+
+
} else {
- yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 270, z: 0 });
+
+ yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 0, z: 0 });
pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 });
rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 });
- yawCorner = { x: right + rotateHandleOffset,
+ yawNormal = { x: 0, y: 1, z: 0 };
+ pitchNormal = { x: 1, y: 0, z: 0 };
+ rollNormal = { x: 0, y: 0, z: 1 };
+
+
+ yawCorner = { x: left + rotateHandleOffset,
y: bottom - rotateHandleOffset,
z: far + rotateHandleOffset };
- pitchCorner = { x: left - rotateHandleOffset,
+ pitchCorner = { x: right - rotateHandleOffset,
y: top + rotateHandleOffset,
z: far + rotateHandleOffset };
- rollCorner = { x: right + rotateHandleOffset,
+ rollCorner = { x: left + rotateHandleOffset,
y: top + rotateHandleOffset,
z: near - rotateHandleOffset};
+
+ yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z };
+ pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z };
+ rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near};
+
+ Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" });
+ Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" });
}
} else {
+
// must be BLF or BLN
- if (MyAvatar.position.z < center.z) {
- yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 });
+ if (MyAvatar.position.z < objectCenter.z) {
+
+ yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 180, z: 0 });
pitchHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 90 });
rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 });
- yawCorner = { x: left - rotateHandleOffset,
+ yawNormal = { x: 0, y: 1, z: 0 };
+ pitchNormal = { x: 1, y: 0, z: 0 };
+ rollNormal = { x: 0, y: 0, z: 1 };
+
+ yawCorner = { x: right - rotateHandleOffset,
y: bottom - rotateHandleOffset,
z: near - rotateHandleOffset };
- pitchCorner = { x: right + rotateHandleOffset,
+ pitchCorner = { x: left + rotateHandleOffset,
y: top + rotateHandleOffset,
z: near - rotateHandleOffset };
- rollCorner = { x: left - rotateHandleOffset,
+ rollCorner = { x: right - rotateHandleOffset,
y: top + rotateHandleOffset,
z: far + rotateHandleOffset};
-
- } else {
- yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 180, z: 0 });
- pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 });
- rollHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 });
+ yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z };
+ pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z };
+ rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far};
- yawCorner = { x: left - rotateHandleOffset,
+ Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" });
+ Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" });
+
+ } else {
+
+ yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 270, z: 0 });
+ rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 });
+ pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 });
+
+ yawNormal = { x: 0, y: 1, z: 0 };
+ rollNormal = { x: 0, y: 0, z: 1 };
+ pitchNormal = { x: 1, y: 0, z: 0 };
+
+ yawCorner = { x: right - rotateHandleOffset,
y: bottom - rotateHandleOffset,
z: far + rotateHandleOffset };
- pitchCorner = { x: left - rotateHandleOffset,
+ rollCorner = { x: right - rotateHandleOffset,
y: top + rotateHandleOffset,
z: near - rotateHandleOffset };
- rollCorner = { x: right + rotateHandleOffset,
+ pitchCorner = { x: left + rotateHandleOffset,
y: top + rotateHandleOffset,
z: far + rotateHandleOffset};
+
+ yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z };
+ rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near };
+ pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z};
+
+ Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" });
+ Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" });
+
}
}
+
+ var rotateHandlesVisible = true;
+ var translateHandlesVisible = true;
+ var stretchHandlesVisible = true;
+ var selectionBoxVisible = true;
+ if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_XZ") {
+ rotateHandlesVisible = false;
+ translateHandlesVisible = false;
+ stretchHandlesVisible = false;
+ selectionBoxVisible = false;
+ } else if (mode == "TRANSLATE_UP_DOWN") {
+ rotateHandlesVisible = false;
+ stretchHandlesVisible = false;
+ } else if (mode != "UNKNOWN") {
+ // every other mode is a stretch mode...
+ rotateHandlesVisible = false;
+ translateHandlesVisible = false;
+ }
Overlays.editOverlay(highlightBox, { visible: false });
- Overlays.editOverlay(selectionBox,
- {
- visible: true,
- position: center,
- dimensions: properties.dimensions,
- rotation: properties.rotation,
- });
+ Overlays.editOverlay(selectionBox, { visible: selectionBoxVisible, position: objectCenter, dimensions: properties.dimensions,
+ rotation: properties.rotation,});
- Overlays.editOverlay(grabberMoveUp, { visible: true, position: { x: center.x, y: top + grabberMoveUpOffset, z: center.z } });
+ Overlays.editOverlay(grabberMoveUp, { visible: translateHandlesVisible, position: { x: boundsCenter.x, y: top + grabberMoveUpOffset, z: boundsCenter.z } });
- Overlays.editOverlay(grabberLBN, { visible: true, position: { x: left, y: bottom, z: near } });
- Overlays.editOverlay(grabberRBN, { visible: true, position: { x: right, y: bottom, z: near } });
- Overlays.editOverlay(grabberLBF, { visible: true, position: { x: left, y: bottom, z: far } });
- Overlays.editOverlay(grabberRBF, { visible: true, position: { x: right, y: bottom, z: far } });
- Overlays.editOverlay(grabberLTN, { visible: true, position: { x: left, y: top, z: near } });
- Overlays.editOverlay(grabberRTN, { visible: true, position: { x: right, y: top, z: near } });
- Overlays.editOverlay(grabberLTF, { visible: true, position: { x: left, y: top, z: far } });
- Overlays.editOverlay(grabberRTF, { visible: true, position: { x: right, y: top, z: far } });
+ Overlays.editOverlay(grabberLBN, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: near } });
+ Overlays.editOverlay(grabberRBN, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: near } });
+ Overlays.editOverlay(grabberLBF, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: far } });
+ Overlays.editOverlay(grabberRBF, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: far } });
+ Overlays.editOverlay(grabberLTN, { visible: stretchHandlesVisible, position: { x: left, y: top, z: near } });
+ Overlays.editOverlay(grabberRTN, { visible: stretchHandlesVisible, position: { x: right, y: top, z: near } });
+ Overlays.editOverlay(grabberLTF, { visible: stretchHandlesVisible, position: { x: left, y: top, z: far } });
+ Overlays.editOverlay(grabberRTF, { visible: stretchHandlesVisible, position: { x: right, y: top, z: far } });
- Overlays.editOverlay(grabberTOP, { visible: true, position: { x: center.x, y: top, z: center.z } });
- Overlays.editOverlay(grabberBOTTOM, { visible: true, position: { x: center.x, y: bottom, z: center.z } });
- Overlays.editOverlay(grabberLEFT, { visible: true, position: { x: left, y: center.y, z: center.z } });
- Overlays.editOverlay(grabberRIGHT, { visible: true, position: { x: right, y: center.y, z: center.z } });
- Overlays.editOverlay(grabberNEAR, { visible: true, position: { x: center.x, y: center.y, z: near } });
- Overlays.editOverlay(grabberFAR, { visible: true, position: { x: center.x, y: center.y, z: far } });
+ Overlays.editOverlay(grabberTOP, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberBOTTOM, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberLEFT, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberRIGHT, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberNEAR, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: boundsCenter.y, z: near } });
+ Overlays.editOverlay(grabberFAR, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: boundsCenter.y, z: far } });
- Overlays.editOverlay(grabberEdgeTR, { visible: true, position: { x: right, y: top, z: center.z } });
- Overlays.editOverlay(grabberEdgeTL, { visible: true, position: { x: left, y: top, z: center.z } });
- Overlays.editOverlay(grabberEdgeTF, { visible: true, position: { x: center.x, y: top, z: far } });
- Overlays.editOverlay(grabberEdgeTN, { visible: true, position: { x: center.x, y: top, z: near } });
- Overlays.editOverlay(grabberEdgeBR, { visible: true, position: { x: right, y: bottom, z: center.z } });
- Overlays.editOverlay(grabberEdgeBL, { visible: true, position: { x: left, y: bottom, z: center.z } });
- Overlays.editOverlay(grabberEdgeBF, { visible: true, position: { x: center.x, y: bottom, z: far } });
- Overlays.editOverlay(grabberEdgeBN, { visible: true, position: { x: center.x, y: bottom, z: near } });
- Overlays.editOverlay(grabberEdgeNR, { visible: true, position: { x: right, y: center.y, z: near } });
- Overlays.editOverlay(grabberEdgeNL, { visible: true, position: { x: left, y: center.y, z: near } });
- Overlays.editOverlay(grabberEdgeFR, { visible: true, position: { x: right, y: center.y, z: far } });
- Overlays.editOverlay(grabberEdgeFL, { visible: true, position: { x: left, y: center.y, z: far } });
+ Overlays.editOverlay(grabberEdgeTR, { visible: stretchHandlesVisible, position: { x: right, y: top, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberEdgeTL, { visible: stretchHandlesVisible, position: { x: left, y: top, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberEdgeTF, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: far } });
+ Overlays.editOverlay(grabberEdgeTN, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: near } });
+ Overlays.editOverlay(grabberEdgeBR, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberEdgeBL, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: boundsCenter.z } });
+ Overlays.editOverlay(grabberEdgeBF, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: far } });
+ Overlays.editOverlay(grabberEdgeBN, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: near } });
+ Overlays.editOverlay(grabberEdgeNR, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: near } });
+ Overlays.editOverlay(grabberEdgeNL, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: near } });
+ Overlays.editOverlay(grabberEdgeFR, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: far } });
+ Overlays.editOverlay(grabberEdgeFL, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: far } });
Overlays.editOverlay(baseOfEntityProjectionOverlay,
@@ -562,14 +692,12 @@ SelectionDisplay = (function () {
rotation: properties.rotation,
});
+
+ Overlays.editOverlay(rotateOverlayTarget, { visible: false });
Overlays.editOverlay(rotateOverlayInner,
{
visible: false,
- position: { x: properties.position.x,
- y: properties.position.y - (properties.dimensions.y / 2),
- z: properties.position.z},
-
size: innerRadius,
innerRadius: 0.9,
alpha: innerAlpha
@@ -578,10 +706,6 @@ SelectionDisplay = (function () {
Overlays.editOverlay(rotateOverlayOuter,
{
visible: false,
- position: { x: properties.position.x,
- y: properties.position.y - (properties.dimensions.y / 2),
- z: properties.position.z},
-
size: outerRadius,
innerRadius: 0.9,
startAt: 0,
@@ -592,20 +716,19 @@ SelectionDisplay = (function () {
Overlays.editOverlay(rotateOverlayCurrent,
{
visible: false,
- position: { x: properties.position.x,
- y: properties.position.y - (properties.dimensions.y / 2),
- z: properties.position.z},
-
size: outerRadius,
startAt: 0,
endAt: 0,
innerRadius: 0.9,
});
+
+ Overlays.editOverlay(rotateZeroOverlay, { visible: false });
+ Overlays.editOverlay(rotateCurrentOverlay, { visible: false });
// TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden
- Overlays.editOverlay(yawHandle, { visible: false, position: yawCorner, rotation: yawHandleRotation});
- Overlays.editOverlay(pitchHandle, { visible: false, position: pitchCorner, rotation: pitchHandleRotation});
- Overlays.editOverlay(rollHandle, { visible: false, position: rollCorner, rotation: rollHandleRotation});
+ Overlays.editOverlay(yawHandle, { visible: rotateHandlesVisible, position: yawCorner, rotation: yawHandleRotation});
+ Overlays.editOverlay(pitchHandle, { visible: rotateHandlesVisible, position: pitchCorner, rotation: pitchHandleRotation});
+ Overlays.editOverlay(rollHandle, { visible: rotateHandlesVisible, position: rollCorner, rotation: rollHandleRotation});
Entities.editEntity(entityID, { localRenderAlpha: 0.1 });
};
@@ -655,10 +778,14 @@ SelectionDisplay = (function () {
Overlays.editOverlay(pitchHandle, { visible: false });
Overlays.editOverlay(rollHandle, { visible: false });
+ Overlays.editOverlay(rotateOverlayTarget, { visible: false });
Overlays.editOverlay(rotateOverlayInner, { visible: false });
Overlays.editOverlay(rotateOverlayOuter, { visible: false });
Overlays.editOverlay(rotateOverlayCurrent, { visible: false });
+ Overlays.editOverlay(rotateZeroOverlay, { visible: false });
+ Overlays.editOverlay(rotateCurrentOverlay, { visible: false });
+
Entities.editEntity(entityID, { localRenderAlpha: 1.0 });
currentSelection = { id: -1, isKnownID: false };
@@ -1440,7 +1567,185 @@ SelectionDisplay = (function () {
Entities.editEntity(currentSelection, selectedEntityProperties);
tooltip.updateText(selectedEntityProperties);
that.select(currentSelection, false); // TODO: this should be more than highlighted
- };
+ };
+
+ that.rotateYaw = function(event) {
+ if (!entitySelected || mode !== "ROTATE_YAW") {
+ return; // not allowed
+ }
+
+ var pickRay = Camera.computePickRay(event.x, event.y);
+ Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false});
+ Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false });
+ Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false });
+ Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true });
+ Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true });
+ Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true });
+
+ var result = Overlays.findRayIntersection(pickRay);
+ if (result.intersects) {
+ var properties = Entities.getEntityProperties(currentSelection);
+ var center = yawCenter;
+ var zero = yawZero;
+ var centerToZero = Vec3.subtract(center, zero);
+ var centerToIntersect = Vec3.subtract(center, result.intersection);
+ var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
+
+ var distanceFromCenter = Vec3.distance(center, result.intersection);
+ var snapToInner = false;
+ if (distanceFromCenter < innerRadius) {
+ angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle;
+ snapToInner = true;
+ }
+
+ // for debugging
+ //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection });
+
+ var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 });
+ var newRotation = Quat.multiply(yawChange, originalRotation);
+
+ Entities.editEntity(currentSelection, { rotation: newRotation });
+
+ // update the rotation display accordingly...
+ var startAtCurrent = 0;
+ var endAtCurrent = angleFromZero;
+ var startAtRemainder = angleFromZero;
+ var endAtRemainder = 360;
+ if (angleFromZero < 0) {
+ startAtCurrent = 360 + angleFromZero;
+ endAtCurrent = 360;
+ startAtRemainder = 0;
+ endAtRemainder = startAtCurrent;
+ }
+ if (snapToInner) {
+ Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 });
+ Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder });
+ Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius });
+ } else {
+ Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 });
+ Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder });
+ Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius });
+ }
+
+ }
+ };
+
+ that.rotatePitch = function(event) {
+ if (!entitySelected || mode !== "ROTATE_PITCH") {
+ return; // not allowed
+ }
+ var pickRay = Camera.computePickRay(event.x, event.y);
+ Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false});
+ Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false });
+ Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false });
+ Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true });
+ Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true });
+ Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true });
+ var result = Overlays.findRayIntersection(pickRay);
+
+ if (result.intersects) {
+ var properties = Entities.getEntityProperties(currentSelection);
+ var center = pitchCenter;
+ var zero = pitchZero;
+ var centerToZero = Vec3.subtract(center, zero);
+ var centerToIntersect = Vec3.subtract(center, result.intersection);
+ var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
+
+ var distanceFromCenter = Vec3.distance(center, result.intersection);
+ var snapToInner = false;
+ if (distanceFromCenter < innerRadius) {
+ angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle;
+ snapToInner = true;
+ }
+
+ // for debugging
+ //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection });
+
+ var pitchChange = Quat.fromVec3Degrees({ x: angleFromZero, y: 0, z: 0 });
+ var newRotation = Quat.multiply(pitchChange, originalRotation);
+
+ Entities.editEntity(currentSelection, { rotation: newRotation });
+
+ // update the rotation display accordingly...
+ var startAtCurrent = 0;
+ var endAtCurrent = angleFromZero;
+ var startAtRemainder = angleFromZero;
+ var endAtRemainder = 360;
+ if (angleFromZero < 0) {
+ startAtCurrent = 360 + angleFromZero;
+ endAtCurrent = 360;
+ startAtRemainder = 0;
+ endAtRemainder = startAtCurrent;
+ }
+ if (snapToInner) {
+ Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 });
+ Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder });
+ Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius });
+ } else {
+ Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 });
+ Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder });
+ Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius });
+ }
+ }
+ };
+
+ that.rotateRoll = function(event) {
+ if (!entitySelected || mode !== "ROTATE_ROLL") {
+ return; // not allowed
+ }
+ var pickRay = Camera.computePickRay(event.x, event.y);
+ Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false});
+ Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false });
+ Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false });
+ Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true });
+ Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true });
+ Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true });
+ var result = Overlays.findRayIntersection(pickRay);
+ if (result.intersects) {
+ var properties = Entities.getEntityProperties(currentSelection);
+ var center = rollCenter;
+ var zero = rollZero;
+ var centerToZero = Vec3.subtract(center, zero);
+ var centerToIntersect = Vec3.subtract(center, result.intersection);
+ var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
+
+ var distanceFromCenter = Vec3.distance(center, result.intersection);
+ var snapToInner = false;
+ if (distanceFromCenter < innerRadius) {
+ angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle;
+ snapToInner = true;
+ }
+
+ // for debugging
+ //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection });
+
+ var rollChange = Quat.fromVec3Degrees({ x: 0, y: 0, z: angleFromZero });
+ var newRotation = Quat.multiply(rollChange, originalRotation);
+
+ Entities.editEntity(currentSelection, { rotation: newRotation });
+
+ // update the rotation display accordingly...
+ var startAtCurrent = 0;
+ var endAtCurrent = angleFromZero;
+ var startAtRemainder = angleFromZero;
+ var endAtRemainder = 360;
+ if (angleFromZero < 0) {
+ startAtCurrent = 360 + angleFromZero;
+ endAtCurrent = 360;
+ startAtRemainder = 0;
+ endAtRemainder = startAtCurrent;
+ }
+ if (snapToInner) {
+ Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 });
+ Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder });
+ Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius });
+ } else {
+ Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 });
+ Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder });
+ Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius });
+ }
+ }
+ };
that.checkMove = function() {
if (currentSelection.isKnownID &&
@@ -1477,6 +1782,36 @@ SelectionDisplay = (function () {
case grabberMoveUp:
mode = "TRANSLATE_UP_DOWN";
somethingClicked = true;
+
+ // in translate mode, we hide our stretch handles...
+ Overlays.editOverlay(grabberLBN, { visible: false });
+ Overlays.editOverlay(grabberLBF, { visible: false });
+ Overlays.editOverlay(grabberRBN, { visible: false });
+ Overlays.editOverlay(grabberRBF, { visible: false });
+ Overlays.editOverlay(grabberLTN, { visible: false });
+ Overlays.editOverlay(grabberLTF, { visible: false });
+ Overlays.editOverlay(grabberRTN, { visible: false });
+ Overlays.editOverlay(grabberRTF, { visible: false });
+
+ Overlays.editOverlay(grabberTOP, { visible: false });
+ Overlays.editOverlay(grabberBOTTOM, { visible: false });
+ Overlays.editOverlay(grabberLEFT, { visible: false });
+ Overlays.editOverlay(grabberRIGHT, { visible: false });
+ Overlays.editOverlay(grabberNEAR, { visible: false });
+ Overlays.editOverlay(grabberFAR, { visible: false });
+
+ Overlays.editOverlay(grabberEdgeTR, { visible: false });
+ Overlays.editOverlay(grabberEdgeTL, { visible: false });
+ Overlays.editOverlay(grabberEdgeTF, { visible: false });
+ Overlays.editOverlay(grabberEdgeTN, { visible: false });
+ Overlays.editOverlay(grabberEdgeBR, { visible: false });
+ Overlays.editOverlay(grabberEdgeBL, { visible: false });
+ Overlays.editOverlay(grabberEdgeBF, { visible: false });
+ Overlays.editOverlay(grabberEdgeBN, { visible: false });
+ Overlays.editOverlay(grabberEdgeNR, { visible: false });
+ Overlays.editOverlay(grabberEdgeNL, { visible: false });
+ Overlays.editOverlay(grabberEdgeFR, { visible: false });
+ Overlays.editOverlay(grabberEdgeFL, { visible: false });
break;
case grabberRBN:
@@ -1559,20 +1894,131 @@ SelectionDisplay = (function () {
}
}
+ // if one of the items above was clicked, then we know we are in translate or stretch mode, and we
+ // should hide our rotate handles...
+ if (somethingClicked) {
+ Overlays.editOverlay(yawHandle, { visible: false });
+ Overlays.editOverlay(pitchHandle, { visible: false });
+ Overlays.editOverlay(rollHandle, { visible: false });
+
+ if (mode != "TRANSLATE_UP_DOWN") {
+ Overlays.editOverlay(grabberMoveUp, { visible: false });
+ }
+ }
+
if (!somethingClicked) {
+
+ print("rotate handle case...");
+
// After testing our stretch handles, then check out rotate handles
Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false });
Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false });
Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false });
var result = Overlays.findRayIntersection(pickRay);
+
+ var overlayOrientation;
+ var overlayCenter;
+
+ var properties = Entities.getEntityProperties(currentSelection);
+ var angles = Quat.safeEulerAngles(properties.rotation);
+ var pitch = angles.x;
+ var yaw = angles.y;
+ var roll = angles.z;
+
+ originalRotation = properties.rotation;
+ originalPitch = pitch;
+ originalYaw = yaw;
+ originalRoll = roll;
+
if (result.intersects) {
switch(result.overlayID) {
+ case yawHandle:
+ mode = "ROTATE_YAW";
+ somethingClicked = true;
+ overlayOrientation = yawHandleRotation;
+ overlayCenter = yawCenter;
+ yawZero = result.intersection;
+ rotationNormal = yawNormal;
+ break;
+
+ case pitchHandle:
+ mode = "ROTATE_PITCH";
+ somethingClicked = true;
+ overlayOrientation = pitchHandleRotation;
+ overlayCenter = pitchCenter;
+ pitchZero = result.intersection;
+ rotationNormal = pitchNormal;
+ break;
+
+ case rollHandle:
+ mode = "ROTATE_ROLL";
+ somethingClicked = true;
+ overlayOrientation = rollHandleRotation;
+ overlayCenter = rollCenter;
+ rollZero = result.intersection;
+ rotationNormal = rollNormal;
+ break;
+
default:
print("mousePressEvent()...... " + overlayNames[result.overlayID]);
mode = "UNKNOWN";
break;
}
}
+
+ print(" somethingClicked:" + somethingClicked);
+ print(" mode:" + mode);
+
+
+ if (somethingClicked) {
+
+ Overlays.editOverlay(rotateOverlayTarget, { visible: true, rotation: overlayOrientation, position: overlayCenter });
+ Overlays.editOverlay(rotateOverlayInner, { visible: true, rotation: overlayOrientation, position: overlayCenter });
+ Overlays.editOverlay(rotateOverlayOuter, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 360 });
+ Overlays.editOverlay(rotateOverlayCurrent, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 0 });
+
+ // for debugging
+ //Overlays.editOverlay(rotateZeroOverlay, { visible: true, start: overlayCenter, end: result.intersection });
+ //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: overlayCenter, end: result.intersection });
+
+ Overlays.editOverlay(yawHandle, { visible: false });
+ Overlays.editOverlay(pitchHandle, { visible: false });
+ Overlays.editOverlay(rollHandle, { visible: false });
+
+
+ Overlays.editOverlay(yawHandle, { visible: false });
+ Overlays.editOverlay(pitchHandle, { visible: false });
+ Overlays.editOverlay(rollHandle, { visible: false });
+ Overlays.editOverlay(grabberMoveUp, { visible: false });
+ Overlays.editOverlay(grabberLBN, { visible: false });
+ Overlays.editOverlay(grabberLBF, { visible: false });
+ Overlays.editOverlay(grabberRBN, { visible: false });
+ Overlays.editOverlay(grabberRBF, { visible: false });
+ Overlays.editOverlay(grabberLTN, { visible: false });
+ Overlays.editOverlay(grabberLTF, { visible: false });
+ Overlays.editOverlay(grabberRTN, { visible: false });
+ Overlays.editOverlay(grabberRTF, { visible: false });
+
+ Overlays.editOverlay(grabberTOP, { visible: false });
+ Overlays.editOverlay(grabberBOTTOM, { visible: false });
+ Overlays.editOverlay(grabberLEFT, { visible: false });
+ Overlays.editOverlay(grabberRIGHT, { visible: false });
+ Overlays.editOverlay(grabberNEAR, { visible: false });
+ Overlays.editOverlay(grabberFAR, { visible: false });
+
+ Overlays.editOverlay(grabberEdgeTR, { visible: false });
+ Overlays.editOverlay(grabberEdgeTL, { visible: false });
+ Overlays.editOverlay(grabberEdgeTF, { visible: false });
+ Overlays.editOverlay(grabberEdgeTN, { visible: false });
+ Overlays.editOverlay(grabberEdgeBR, { visible: false });
+ Overlays.editOverlay(grabberEdgeBL, { visible: false });
+ Overlays.editOverlay(grabberEdgeBF, { visible: false });
+ Overlays.editOverlay(grabberEdgeBN, { visible: false });
+ Overlays.editOverlay(grabberEdgeNR, { visible: false });
+ Overlays.editOverlay(grabberEdgeNL, { visible: false });
+ Overlays.editOverlay(grabberEdgeFR, { visible: false });
+ Overlays.editOverlay(grabberEdgeFL, { visible: false });
+ }
}
if (!somethingClicked) {
@@ -1614,6 +2060,15 @@ SelectionDisplay = (function () {
that.mouseMoveEvent = function(event) {
//print("mouseMoveEvent()... mode:" + mode);
switch (mode) {
+ case "ROTATE_YAW":
+ that.rotateYaw(event);
+ break;
+ case "ROTATE_PITCH":
+ that.rotatePitch(event);
+ break;
+ case "ROTATE_ROLL":
+ that.rotateRoll(event);
+ break;
case "TRANSLATE_UP_DOWN":
that.translateUpDown(event);
break;
@@ -1671,14 +2126,34 @@ SelectionDisplay = (function () {
};
that.mouseReleaseEvent = function(event) {
+ var showHandles = false;
+ // hide our rotation overlays..., and show our handles
+ if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") {
+ Overlays.editOverlay(rotateOverlayTarget, { visible: false });
+ Overlays.editOverlay(rotateOverlayInner, { visible: false });
+ Overlays.editOverlay(rotateOverlayOuter, { visible: false });
+ Overlays.editOverlay(rotateOverlayCurrent, { visible: false });
+ showHandles = true;
+ }
+
+ if (mode != "UNKNOWN") {
+ showHandles = true;
+ }
+
mode = "UNKNOWN";
// if something is selected, then reset the "original" properties for any potential next click+move operation
if (entitySelected) {
+
+ if (showHandles) {
+ that.select(currentSelection, event);
+ }
+
selectedEntityProperties = Entities.getEntityProperties(currentSelection);
selectedEntityPropertiesOriginalPosition = properties.position;
selectedEntityPropertiesOriginalDimensions = properties.dimensions;
}
+
};
Controller.mousePressEvent.connect(that.mousePressEvent);
diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js
index a223813347..b753537631 100644
--- a/examples/newEditEntities.js
+++ b/examples/newEditEntities.js
@@ -570,6 +570,19 @@ function handeMenuEvent(menuItem) {
}
} else if (menuItem == "Edit Properties...") {
// good place to put the properties dialog
+
+ editModelID = -1;
+ if (entitySelected) {
+ print(" Edit Properties.... selectedEntityID="+ selectedEntityID);
+ editModelID = selectedEntityID;
+ } else {
+ print(" Edit Properties.... not holding...");
+ }
+ if (editModelID != -1) {
+ print(" Edit Properties.... about to edit properties...");
+ entityPropertyDialogBox.openDialog(editModelID);
+ }
+
} else if (menuItem == "Paste Models") {
modelImporter.paste();
} else if (menuItem == "Export Models") {
diff --git a/examples/xbox.js b/examples/xbox.js
deleted file mode 100644
index 603e0dbf56..0000000000
--- a/examples/xbox.js
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// xbox.js
-// examples
-//
-// Created by Stephen Birarda on September 23, 2014
-//
-// Copyright 2014 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-gamepad = Joysticks.joystickWithName("Wireless 360 Controller");
-
-function reportAxisValue(axis, newValue, oldValue) {
- print("The value for axis " + axis + " has changed to " + newValue + ". It was " + oldValue);
-}
-
-gamepad.axisValueChanged.connect(reportAxisValue);
\ No newline at end of file
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index 19f8ef6d5f..1202b36a9f 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -41,7 +41,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe
# grab the implementation and header files from src dirs
file(GLOB INTERFACE_SRCS src/*.cpp src/*.h)
-foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities)
+foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities gpu)
file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h)
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}")
endforeach(SUBDIR)
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 718360864a..55fe438cfe 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -605,10 +605,14 @@ void Application::paintGL() {
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
if (!OculusManager::isConnected()) {
+ // If there isn't an HMD, match exactly to avatar's head
_myCamera.setPosition(_myAvatar->getHead()->getEyePosition());
_myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation());
+ } else {
+ // For an HMD, set the base position and orientation to that of the avatar body
+ _myCamera.setPosition(_myAvatar->getDefaultEyePosition());
+ _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation());
}
- // OculusManager::display() updates camera position and rotation a bit further on.
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
static const float THIRD_PERSON_CAMERA_DISTANCE = 1.5f;
@@ -664,7 +668,6 @@ void Application::paintGL() {
_viewFrustumOffsetCamera.setRotation(_myCamera.getRotation() * frustumRotation);
- _viewFrustumOffsetCamera.initialize(); // force immediate snap to ideal position and orientation
_viewFrustumOffsetCamera.update(1.f/_fps);
whichCamera = &_viewFrustumOffsetCamera;
}
diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp
index 79d66568bf..a8138363fa 100644
--- a/interface/src/Camera.cpp
+++ b/interface/src/Camera.cpp
@@ -22,13 +22,16 @@
Camera::Camera() :
- _needsToInitialize(true),
_mode(CAMERA_MODE_THIRD_PERSON),
_position(0.0f, 0.0f, 0.0f),
_fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES),
_aspectRatio(16.0f/9.0f),
_nearClip(DEFAULT_NEAR_CLIP), // default
_farClip(DEFAULT_FAR_CLIP), // default
+ _hmdPosition(),
+ _hmdRotation(),
+ _targetPosition(),
+ _targetRotation(),
_scale(1.0f)
{
}
@@ -64,26 +67,6 @@ void Camera::setFarClip(float f) {
_farClip = f;
}
-void Camera::setEyeOffsetPosition(const glm::vec3& p) {
- _eyeOffsetPosition = p;
-}
-
-void Camera::setEyeOffsetOrientation(const glm::quat& o) {
- _eyeOffsetOrientation = o;
-
-}
-
-void Camera::setScale(float s) {
- _scale = s;
- _needsToInitialize = true;
-
-}
-
-void Camera::initialize() {
- _needsToInitialize = true;
-}
-
-
CameraScriptableObject::CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum) :
_camera(camera), _viewFrustum(viewFrustum)
{
diff --git a/interface/src/Camera.h b/interface/src/Camera.h
index ee4930272d..80454a969e 100644
--- a/interface/src/Camera.h
+++ b/interface/src/Camera.h
@@ -38,17 +38,23 @@ public:
void setPosition(const glm::vec3& p) { _position = p; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; };
+ void setHmdPosition(const glm::vec3& hmdPosition) { _hmdPosition = hmdPosition; }
+ void setHmdRotation(const glm::quat& hmdRotation) { _hmdRotation = hmdRotation; };
+
void setMode(CameraMode m);
void setFieldOfView(float f);
void setAspectRatio(float a);
void setNearClip(float n);
void setFarClip(float f);
- void setEyeOffsetPosition(const glm::vec3& p);
- void setEyeOffsetOrientation(const glm::quat& o);
- void setScale(const float s);
+ void setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; }
+ void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; }
+ void setScale(const float s) { _scale = s; }
+
+ glm::vec3 getPosition() const { return _position + _hmdPosition; }
+ glm::quat getRotation() const { return _rotation * _hmdRotation; }
+ const glm::vec3& getHmdPosition() const { return _hmdPosition; }
+ const glm::quat& getHmdRotation() const { return _hmdRotation; }
- const glm::vec3& getPosition() const { return _position; }
- const glm::quat& getRotation() const { return _rotation; }
CameraMode getMode() const { return _mode; }
float getFieldOfView() const { return _fieldOfView; }
float getAspectRatio() const { return _aspectRatio; }
@@ -60,7 +66,6 @@ public:
private:
- bool _needsToInitialize;
CameraMode _mode;
glm::vec3 _position;
float _fieldOfView; // degrees
@@ -70,7 +75,10 @@ private:
glm::vec3 _eyeOffsetPosition;
glm::quat _eyeOffsetOrientation;
glm::quat _rotation;
-
+ glm::vec3 _hmdPosition;
+ glm::quat _hmdRotation;
+ glm::vec3 _targetPosition;
+ glm::quat _targetRotation;
float _scale;
};
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index c0ce474d16..36035880fd 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -1151,7 +1151,7 @@ const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f;
bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const {
const Head* head = getHead();
- return (renderMode != NORMAL_RENDER_MODE) ||
+ return (renderMode != NORMAL_RENDER_MODE) || (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON) ||
(glm::length(cameraPosition - head->getEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale);
}
diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp
index eb48e3d463..ad6a914112 100644
--- a/interface/src/devices/OculusManager.cpp
+++ b/interface/src/devices/OculusManager.cpp
@@ -337,15 +337,20 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
#else
ovrEyeType eye = _ovrHmdDesc.EyeRenderOrder[eyeIndex];
#endif
- //Set the camera rotation for this eye
+ // Set the camera rotation for this eye
eyeRenderPose[eye] = ovrHmd_GetEyePose(_ovrHmd, eye);
orientation.x = eyeRenderPose[eye].Orientation.x;
orientation.y = eyeRenderPose[eye].Orientation.y;
orientation.z = eyeRenderPose[eye].Orientation.z;
orientation.w = eyeRenderPose[eye].Orientation.w;
- _camera->setRotation(bodyOrientation * orientation);
- _camera->setPosition(position + trackerPosition);
+ // Update the application camera with the latest HMD position
+ whichCamera.setHmdPosition(trackerPosition);
+ whichCamera.setHmdRotation(orientation);
+
+ // Update our camera to what the application camera is doing
+ _camera->setRotation(whichCamera.getRotation());
+ _camera->setPosition(whichCamera.getPosition());
// Store the latest left and right eye render locations for things that need to know
glm::vec3 thisEyePosition = position + trackerPosition +
@@ -407,10 +412,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
renderDistortionMesh(eyeRenderPose);
glBindTexture(GL_TEXTURE_2D, 0);
-
- // Update camera for use by rest of Interface.
- whichCamera.setPosition((_leftEyePosition + _rightEyePosition) / 2.f);
- whichCamera.setRotation(_camera->getRotation());
+
#endif
}
diff --git a/interface/src/gpu/Resource.cpp b/interface/src/gpu/Resource.cpp
new file mode 100644
index 0000000000..23ea12e6e8
--- /dev/null
+++ b/interface/src/gpu/Resource.cpp
@@ -0,0 +1,220 @@
+//
+// Resource.cpp
+// interface/src/gpu
+//
+// Created by Sam Gateau on 10/8/2014.
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "Resource.h"
+
+#include
+
+using namespace gpu;
+
+Resource::Size Resource::Sysmem::allocateMemory(Byte** dataAllocated, Size size) {
+ if ( !dataAllocated ) {
+ qWarning() << "Buffer::Sysmem::allocateMemory() : Must have a valid dataAllocated pointer.";
+ return NOT_ALLOCATED;
+ }
+
+ // Try to allocate if needed
+ Size newSize = 0;
+ if (size > 0) {
+ // Try allocating as much as the required size + one block of memory
+ newSize = size;
+ (*dataAllocated) = new Byte[newSize];
+ // Failed?
+ if (!(*dataAllocated)) {
+ qWarning() << "Buffer::Sysmem::allocate() : Can't allocate a system memory buffer of " << newSize << "bytes. Fails to create the buffer Sysmem.";
+ return NOT_ALLOCATED;
+ }
+ }
+
+ // Return what's actually allocated
+ return newSize;
+}
+
+void Resource::Sysmem::deallocateMemory(Byte* dataAllocated, Size size) {
+ if (dataAllocated) {
+ delete[] dataAllocated;
+ }
+}
+
+Resource::Sysmem::Sysmem() :
+ _data(NULL),
+ _size(0),
+ _stamp(0)
+{
+}
+
+Resource::Sysmem::Sysmem(Size size, const Byte* bytes) :
+ _data(NULL),
+ _size(0),
+ _stamp(0)
+{
+ if (size > 0) {
+ _size = allocateMemory(&_data, size);
+ if (_size >= size) {
+ if (bytes) {
+ memcpy(_data, bytes, size);
+ }
+ }
+ }
+}
+
+Resource::Sysmem::~Sysmem() {
+ deallocateMemory( _data, _size );
+ _data = NULL;
+ _size = 0;
+}
+
+Resource::Size Resource::Sysmem::allocate(Size size) {
+ if (size != _size) {
+ Byte* newData = 0;
+ Size newSize = 0;
+ if (size > 0) {
+ Size allocated = allocateMemory(&newData, size);
+ if (allocated == NOT_ALLOCATED) {
+ // early exit because allocation failed
+ return 0;
+ }
+ newSize = allocated;
+ }
+ // Allocation was successful, can delete previous data
+ deallocateMemory(_data, _size);
+ _data = newData;
+ _size = newSize;
+ _stamp++;
+ }
+ return _size;
+}
+
+Resource::Size Resource::Sysmem::resize(Size size) {
+ if (size != _size) {
+ Byte* newData = 0;
+ Size newSize = 0;
+ if (size > 0) {
+ Size allocated = allocateMemory(&newData, size);
+ if (allocated == NOT_ALLOCATED) {
+ // early exit because allocation failed
+ return _size;
+ }
+ newSize = allocated;
+ // Restore back data from old buffer in the new one
+ if (_data) {
+ Size copySize = ((newSize < _size)? newSize: _size);
+ memcpy( newData, _data, copySize);
+ }
+ }
+ // Reallocation was successful, can delete previous data
+ deallocateMemory(_data, _size);
+ _data = newData;
+ _size = newSize;
+ _stamp++;
+ }
+ return _size;
+}
+
+Resource::Size Resource::Sysmem::setData( Size size, const Byte* bytes ) {
+ if (allocate(size) == size) {
+ if (bytes) {
+ memcpy( _data, bytes, _size );
+ _stamp++;
+ }
+ }
+ return _size;
+}
+
+Resource::Size Resource::Sysmem::setSubData( Size offset, Size size, const Byte* bytes) {
+ if (((offset + size) <= getSize()) && bytes) {
+ memcpy( _data + offset, bytes, size );
+ _stamp++;
+ return size;
+ }
+ return 0;
+}
+
+Resource::Size Resource::Sysmem::append(Size size, const Byte* bytes) {
+ if (size > 0) {
+ Size oldSize = getSize();
+ Size totalSize = oldSize + size;
+ if (resize(totalSize) == totalSize) {
+ return setSubData(oldSize, size, bytes);
+ }
+ }
+ return 0;
+}
+
+Buffer::Buffer() :
+ Resource(),
+ _sysmem(NULL),
+ _gpuObject(NULL) {
+ _sysmem = new Sysmem();
+}
+
+Buffer::~Buffer() {
+ if (_sysmem) {
+ delete _sysmem;
+ _sysmem = 0;
+ }
+ if (_gpuObject) {
+ delete _gpuObject;
+ _gpuObject = 0;
+ }
+}
+
+Buffer::Size Buffer::resize(Size size) {
+ return editSysmem().resize(size);
+}
+
+Buffer::Size Buffer::setData(Size size, const Byte* data) {
+ return editSysmem().setData(size, data);
+}
+
+Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) {
+ return editSysmem().setSubData( offset, size, data);
+}
+
+Buffer::Size Buffer::append(Size size, const Byte* data) {
+ return editSysmem().append( size, data);
+}
+
+namespace gpu {
+namespace backend {
+
+BufferObject::~BufferObject() {
+ if (_buffer!=0) {
+ glDeleteBuffers(1, &_buffer);
+ }
+}
+
+void syncGPUObject(const Buffer& buffer) {
+ BufferObject* object = buffer.getGPUObject();
+
+ if (object && (object->_stamp == buffer.getSysmem().getStamp())) {
+ return;
+ }
+
+ // need to have a gpu object?
+ if (!object) {
+ object = new BufferObject();
+ glGenBuffers(1, &object->_buffer);
+ buffer.setGPUObject(object);
+ }
+
+ // Now let's update the content of the bo with the sysmem version
+ // TODO: in the future, be smarter about when to actually upload the glBO version based on the data that did change
+ //if () {
+ glBindBuffer(GL_ARRAY_BUFFER, object->_buffer);
+ glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ object->_stamp = buffer.getSysmem().getStamp();
+ object->_size = buffer.getSysmem().getSize();
+ //}
+}
+
+};
+};
diff --git a/interface/src/gpu/Resource.h b/interface/src/gpu/Resource.h
new file mode 100644
index 0000000000..fbbbe7f7fd
--- /dev/null
+++ b/interface/src/gpu/Resource.h
@@ -0,0 +1,166 @@
+//
+// Resource.h
+// interface/src/gpu
+//
+// Created by Sam Gateau on 10/8/2014.
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#ifndef hifi_gpu_Resource_h
+#define hifi_gpu_Resource_h
+
+#include
+#include "InterfaceConfig.h"
+
+namespace gpu {
+
+class Buffer;
+typedef int Stamp;
+
+// TODO: move the backend namespace into dedicated files, for now we keep it close to the gpu objects definition for convenience
+namespace backend {
+
+ class BufferObject {
+ public:
+ Stamp _stamp;
+ GLuint _buffer;
+ GLuint _size;
+
+ BufferObject() :
+ _stamp(0),
+ _buffer(0),
+ _size(0)
+ {}
+
+ ~BufferObject();
+ };
+ void syncGPUObject(const Buffer& buffer);
+};
+
+class Resource {
+public:
+ typedef unsigned char Byte;
+ typedef unsigned int Size;
+
+ static const Size NOT_ALLOCATED = -1;
+
+ // The size in bytes of data stored in the resource
+ virtual Size getSize() const = 0;
+
+protected:
+
+ Resource() {}
+ virtual ~Resource() {}
+
+ // Sysmem is the underneath cache for the data in ram of a resource.
+ class Sysmem {
+ public:
+
+ Sysmem();
+ Sysmem(Size size, const Byte* bytes);
+ ~Sysmem();
+
+ Size getSize() const { return _size; }
+
+ // Allocate the byte array
+ // \param pSize The nb of bytes to allocate, if already exist, content is lost.
+ // \return The nb of bytes allocated, nothing if allready the appropriate size.
+ Size allocate(Size pSize);
+
+ // Resize the byte array
+ // Keep previous data [0 to min(pSize, mSize)]
+ Size resize(Size pSize);
+
+ // Assign data bytes and size (allocate for size, then copy bytes if exists)
+ Size setData(Size size, const Byte* bytes );
+
+ // Update Sub data,
+ // doesn't allocate and only copy size * bytes at the offset location
+ // only if all fits in the existing allocated buffer
+ Size setSubData(Size offset, Size size, const Byte* bytes);
+
+ // Append new data at the end of the current buffer
+ // do a resize( size + getSIze) and copy the new data
+ // \return the number of bytes copied
+ Size append(Size size, const Byte* data);
+
+ // Access the byte array.
+ // The edit version allow to map data.
+ inline const Byte* readData() const { return _data; }
+ inline Byte* editData() { _stamp++; return _data; }
+
+ template< typename T >
+ const T* read() const { return reinterpret_cast< T* > ( _data ); }
+ template< typename T >
+ T* edit() { _stamp++; return reinterpret_cast< T* > ( _data ); }
+
+ // Access the current version of the sysmem, used to compare if copies are in sync
+ inline Stamp getStamp() const { return _stamp; }
+
+ static Size allocateMemory(Byte** memAllocated, Size size);
+ static void deallocateMemory(Byte* memDeallocated, Size size);
+
+ private:
+ Sysmem(const Sysmem& sysmem) {}
+ Sysmem &operator=(const Sysmem& other) {return *this;}
+
+ Stamp _stamp;
+ Size _size;
+ Byte* _data;
+ };
+
+};
+
+class Buffer : public Resource {
+public:
+
+ Buffer();
+ Buffer(const Buffer& buf);
+ ~Buffer();
+
+ // The size in bytes of data stored in the buffer
+ inline Size getSize() const { return getSysmem().getSize(); }
+ inline const Byte* getData() const { return getSysmem().readData(); }
+
+ // Resize the buffer
+ // Keep previous data [0 to min(pSize, mSize)]
+ Size resize(Size pSize);
+
+ // Assign data bytes and size (allocate for size, then copy bytes if exists)
+ // \return the size of the buffer
+ Size setData(Size size, const Byte* data);
+
+ // Assign data bytes and size (allocate for size, then copy bytes if exists)
+ // \return the number of bytes copied
+ Size setSubData(Size offset, Size size, const Byte* data);
+
+ // Append new data at the end of the current buffer
+ // do a resize( size + getSize) and copy the new data
+ // \return the number of bytes copied
+ Size append(Size size, const Byte* data);
+
+ // this is a temporary hack so the current rendering code can access the underneath gl Buffer Object
+ // TODO: remove asap, when the backend is doing more of the gl features
+ inline GLuint getGLBufferObject() const { backend::syncGPUObject(*this); return getGPUObject()->_buffer; }
+
+protected:
+
+ Sysmem* _sysmem;
+
+ typedef backend::BufferObject GPUObject;
+ mutable backend::BufferObject* _gpuObject;
+
+ inline const Sysmem& getSysmem() const { assert(_sysmem); return (*_sysmem); }
+ inline Sysmem& editSysmem() { assert(_sysmem); return (*_sysmem); }
+
+ inline GPUObject* getGPUObject() const { return _gpuObject; }
+ inline void setGPUObject(GPUObject* gpuObject) const { _gpuObject = gpuObject; }
+
+ friend void backend::syncGPUObject(const Buffer& buffer);
+};
+
+};
+
+#endif
diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp
index 6b7d499bf9..dd9ac67837 100644
--- a/interface/src/ui/Stats.cpp
+++ b/interface/src/ui/Stats.cpp
@@ -690,7 +690,6 @@ void Stats::display(
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
}
-
}
void Stats::setMetavoxelStats(int internal, int leaves, int sendProgress,
diff --git a/interface/src/ui/TextRenderer.cpp b/interface/src/ui/TextRenderer.cpp
index c4f6b328ef..8c78a06d55 100644
--- a/interface/src/ui/TextRenderer.cpp
+++ b/interface/src/ui/TextRenderer.cpp
@@ -21,6 +21,10 @@
#include "InterfaceConfig.h"
#include "TextRenderer.h"
+#include "glm/glm.hpp"
+#include
+
+
// the width/height of the cached glyph textures
const int IMAGE_SIZE = 256;
@@ -63,8 +67,17 @@ int TextRenderer::calculateHeight(const char* str) {
}
int TextRenderer::draw(int x, int y, const char* str) {
- glEnable(GL_TEXTURE_2D);
-
+ // Grab the current color
+ float currentColor[4];
+ glGetFloatv(GL_CURRENT_COLOR, currentColor);
+ int compactColor = ((int( currentColor[0] * 255.f) & 0xFF)) |
+ ((int( currentColor[1] * 255.f) & 0xFF) << 8) |
+ ((int( currentColor[2] * 255.f) & 0xFF) << 16) |
+ ((int( currentColor[3] * 255.f) & 0xFF) << 24);
+
+// TODO: Remove that code once we test for performance improvments
+ //glEnable(GL_TEXTURE_2D);
+
int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
@@ -76,20 +89,24 @@ int TextRenderer::draw(int x, int y, const char* str) {
if (glyph.bounds().height() > maxHeight) {
maxHeight = glyph.bounds().height();
}
-
- glBindTexture(GL_TEXTURE_2D, glyph.textureID());
+ //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();
-
+
+ glm::vec2 leftBottom = glm::vec2(float(left), float(bottom));
+ glm::vec2 rightTop = glm::vec2(float(right), float(top));
+
float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / 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;
-
+
+// TODO: Remove that code once we test for performance improvments
+/*
glBegin(GL_QUADS);
glTexCoord2f(ls, bt);
glVertex2f(left, bottom);
@@ -100,12 +117,39 @@ int TextRenderer::draw(int x, int y, const char* str) {
glTexCoord2f(ls, tt);
glVertex2f(left, top);
glEnd();
-
+*/
+
+ const int NUM_COORDS_SCALARS_PER_GLYPH = 16;
+ float vertexBuffer[NUM_COORDS_SCALARS_PER_GLYPH] = { leftBottom.x, leftBottom.y, ls, bt,
+ rightTop.x, leftBottom.y, rs, bt,
+ rightTop.x, rightTop.y, rs, tt,
+ leftBottom.x, rightTop.y, ls, tt, };
+
+ const int NUM_COLOR_SCALARS_PER_GLYPH = 4;
+ unsigned int colorBuffer[NUM_COLOR_SCALARS_PER_GLYPH] = { compactColor, compactColor, compactColor, compactColor };
+
+ gpu::Buffer::Size offset = sizeof(vertexBuffer) * _numGlyphsBatched;
+ gpu::Buffer::Size colorOffset = sizeof(colorBuffer) * _numGlyphsBatched;
+ if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer.getSize()) {
+ _glyphsBuffer.append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
+ _glyphsColorBuffer.append(sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
+ } else {
+ _glyphsBuffer.setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
+ _glyphsColorBuffer.setSubData(colorOffset, sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
+ }
+ _numGlyphsBatched++;
+
x += glyph.width();
}
- glBindTexture(GL_TEXTURE_2D, 0);
- glDisable(GL_TEXTURE_2D);
-
+
+ // TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call
+ drawBatch();
+ clearBatch();
+
+// TODO: Remove that code once we test for performance improvments
+ // glBindTexture(GL_TEXTURE_2D, 0);
+ // glDisable(GL_TEXTURE_2D);
+
return maxHeight;
}
@@ -131,8 +175,10 @@ TextRenderer::TextRenderer(const Properties& properties) :
_x(IMAGE_SIZE),
_y(IMAGE_SIZE),
_rowHeight(0),
- _color(properties.color) {
-
+ _color(properties.color),
+ _glyphsBuffer(),
+ _numGlyphsBatched(0)
+{
_font.setKerning(false);
}
@@ -228,9 +274,74 @@ const Glyph& TextRenderer::getGlyph(char c) {
return glyph;
}
+void TextRenderer::drawBatch() {
+ if (_numGlyphsBatched <= 0) {
+ return;
+ }
+
+ // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack
+ /*
+ GLint matrixMode;
+ glGetIntegerv(GL_MATRIX_MODE, &matrixMode);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ */
+
+ glEnable(GL_TEXTURE_2D);
+ // TODO: Apply the correct font atlas texture, for now only one texture per TextRenderer so it should be good
+ glBindTexture(GL_TEXTURE_2D, _currentTextureID);
+
+ GLuint vbo = _glyphsBuffer.getGLBufferObject();
+ GLuint colorvbo = _glyphsColorBuffer.getGLBufferObject();
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ const int NUM_POS_COORDS = 2;
+ const int NUM_TEX_COORDS = 2;
+ const int VERTEX_STRIDE = (NUM_POS_COORDS + NUM_TEX_COORDS) * sizeof(float);
+ const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float);
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glVertexPointer(2, GL_FLOAT, VERTEX_STRIDE, 0);
+ glTexCoordPointer(2, GL_FLOAT, VERTEX_STRIDE, (GLvoid*) VERTEX_TEXCOORD_OFFSET );
+
+ glBindBuffer(GL_ARRAY_BUFFER, colorvbo);
+ glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*) 0 );
+
+ glDrawArrays(GL_QUADS, 0, _numGlyphsBatched * 4);
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glDisable(GL_TEXTURE_2D);
+
+ // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack
+ /*
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+ glMatrixMode(matrixMode);
+ */
+}
+
+void TextRenderer::clearBatch() {
+ _numGlyphsBatched = 0;
+}
+
QHash TextRenderer::_instances;
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
}
-
diff --git a/interface/src/ui/TextRenderer.h b/interface/src/ui/TextRenderer.h
index d3340462d5..1953dda422 100644
--- a/interface/src/ui/TextRenderer.h
+++ b/interface/src/ui/TextRenderer.h
@@ -19,6 +19,8 @@
#include
#include
+#include "gpu/Resource.h"
+
#include "InterfaceConfig.h"
// a special "character" that renders as a solid block
@@ -64,6 +66,8 @@ public:
int computeWidth(char ch);
int computeWidth(const char* str);
+ void drawBatch();
+ void clearBatch();
private:
TextRenderer(const Properties& properties);
@@ -100,6 +104,11 @@ private:
// text color
QColor _color;
+ // Graphics Buffer containing the current accumulated glyphs to render
+ gpu::Buffer _glyphsBuffer;
+ gpu::Buffer _glyphsColorBuffer;
+ int _numGlyphsBatched;
+
static QHash _instances;
};
diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp
index 4f9e7c84f6..c7511ca466 100644
--- a/interface/src/ui/overlays/BillboardOverlay.cpp
+++ b/interface/src/ui/overlays/BillboardOverlay.cpp
@@ -18,7 +18,8 @@
BillboardOverlay::BillboardOverlay()
: _fromImage(-1,-1,-1,-1),
_scale(1.0f),
- _isFacingAvatar(true) {
+ _isFacingAvatar(true),
+ _newTextureNeeded(true) {
_isLoaded = false;
}
@@ -28,6 +29,9 @@ void BillboardOverlay::render() {
}
if (!_billboard.isEmpty()) {
+ if (_newTextureNeeded && _billboardTexture) {
+ _billboardTexture.reset();
+ }
if (!_billboardTexture) {
QImage image = QImage::fromData(_billboard);
if (image.format() != QImage::Format_ARGB32) {
@@ -38,6 +42,7 @@ void BillboardOverlay::render() {
_fromImage.setRect(0, 0, _size.width(), _size.height());
}
_billboardTexture.reset(new Texture());
+ _newTextureNeeded = false;
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0,
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
@@ -107,9 +112,10 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) {
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
- _url = urlValue.toVariant().toString();
-
- setBillboardURL(_url);
+ QString newURL = urlValue.toVariant().toString();
+ if (newURL != _url) {
+ setBillboardURL(newURL);
+ }
}
QScriptValue subImageBounds = properties.property("subImage");
@@ -150,9 +156,16 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) {
}
}
-void BillboardOverlay::setBillboardURL(const QUrl url) {
+void BillboardOverlay::setBillboardURL(const QString& url) {
+ _url = url;
+ QUrl actualURL = url;
_isLoaded = false;
- QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(url));
+
+ // clear the billboard if previously set
+ _billboard.clear();
+ _newTextureNeeded = true;
+
+ QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(actualURL));
connect(reply, &QNetworkReply::finished, this, &BillboardOverlay::replyFinished);
}
diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h
index a0b76869b3..fd594abe67 100644
--- a/interface/src/ui/overlays/BillboardOverlay.h
+++ b/interface/src/ui/overlays/BillboardOverlay.h
@@ -33,12 +33,13 @@ private slots:
void replyFinished();
private:
- void setBillboardURL(const QUrl url);
+ void setBillboardURL(const QString& url);
- QUrl _url;
+ QString _url;
QByteArray _billboard;
QSize _size;
QScopedPointer _billboardTexture;
+ bool _newTextureNeeded;
QRect _fromImage; // where from in the image to sample
diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp
index 6ff256d48e..62ddee4cf2 100644
--- a/interface/src/ui/overlays/Circle3DOverlay.cpp
+++ b/interface/src/ui/overlays/Circle3DOverlay.cpp
@@ -13,6 +13,7 @@
#include
#include
+#include
#include "Circle3DOverlay.h"
#include "renderer/GlowEffect.h"
@@ -39,13 +40,18 @@ void Circle3DOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
+
+ float alpha = getAlpha();
+
+ if (alpha == 0.0) {
+ return; // do nothing if our alpha is 0, we're not visible
+ }
const float FULL_CIRCLE = 360.0f;
const float SLICES = 180.0f; // The amount of segment to create the circle
const float SLICE_ANGLE = FULL_CIRCLE / SLICES;
//const int slices = 15;
- float alpha = getAlpha();
xColor color = getColor();
const float MAX_COLOR = 255.0f;
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
@@ -291,6 +297,25 @@ void Circle3DOverlay::setProperties(const QScriptValue &properties) {
}
}
+bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin,
+ const glm::vec3& direction, float& distance, BoxFace& face) const {
+
+ bool intersects = Planar3DOverlay::findRayIntersection(origin, direction, distance, face);
+ if (intersects) {
+ glm::vec3 hitAt = origin + (direction * distance);
+ float distanceToHit = glm::distance(hitAt, _position);
+
+ float maxDimension = glm::max(_dimensions.x, _dimensions.y);
+ float innerRadius = maxDimension * getInnerRadius();
+ float outerRadius = maxDimension * getOuterRadius();
+
+ // TODO: this really only works for circles, we should be handling the ellipse case as well...
+ if (distanceToHit < innerRadius || distanceToHit > outerRadius) {
+ intersects = false;
+ }
+ }
+ return intersects;
+}
diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h
index 791d951105..289781cdd7 100644
--- a/interface/src/ui/overlays/Circle3DOverlay.h
+++ b/interface/src/ui/overlays/Circle3DOverlay.h
@@ -45,6 +45,8 @@ public:
void setMinorTickMarksLength(float value) { _minorTickMarksLength = value; }
void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; }
void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; }
+
+ virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;
protected:
float _startAt;
diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp
index 42e059c3ca..91a3a023f7 100644
--- a/interface/src/ui/overlays/Planar3DOverlay.cpp
+++ b/interface/src/ui/overlays/Planar3DOverlay.cpp
@@ -12,6 +12,8 @@
#include "InterfaceConfig.h"
#include
+#include
+#include
#include
#include
@@ -74,3 +76,29 @@ void Planar3DOverlay::setProperties(const QScriptValue& properties) {
}
}
}
+
+bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
+ float& distance, BoxFace& face) const {
+
+ RayIntersectionInfo rayInfo;
+ rayInfo._rayStart = origin;
+ rayInfo._rayDirection = direction;
+ rayInfo._rayLength = std::numeric_limits::max();
+
+ PlaneShape plane;
+
+ const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f);
+ glm::vec3 normal = _rotation * UNROTATED_NORMAL;
+ plane.setNormal(normal);
+ plane.setPoint(_position); // the position is definitely a point on our plane
+
+ bool intersects = plane.findRayIntersection(rayInfo);
+
+ if (intersects) {
+ distance = rayInfo._hitDistance;
+ // TODO: if it intersects, we want to check to see if the intersection point is within our dimensions
+ // glm::vec3 hitAt = origin + direction * distance;
+ // _dimensions
+ }
+ return intersects;
+}
diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h
index c1733bc0fb..ee4bb3e05a 100644
--- a/interface/src/ui/overlays/Planar3DOverlay.h
+++ b/interface/src/ui/overlays/Planar3DOverlay.h
@@ -37,6 +37,8 @@ public:
virtual void setProperties(const QScriptValue& properties);
+ virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;
+
protected:
glm::vec2 _dimensions;
};
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 1080a866f5..2690bcd45b 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -174,6 +174,16 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
sittingPoints.setProperty("length", _sittingPoints.size());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable
+ AABox aaBox = getAABoxInMeters();
+ QScriptValue boundingBox = engine->newObject();
+ QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner());
+ QScriptValue topFarLeft = vec3toScriptValue(engine, aaBox.calcTopFarLeft());
+ QScriptValue center = vec3toScriptValue(engine, aaBox.calcCenter());
+ boundingBox.setProperty("brn", bottomRightNear);
+ boundingBox.setProperty("tfl", topFarLeft);
+ boundingBox.setProperty("center", center);
+ COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(boundingBox, boundingBox); // gettable, but not settable
+
return properties;
}
@@ -643,3 +653,20 @@ AACube EntityItemProperties::getMaximumAACubeInMeters() const {
return AACube(minimumCorner, diameter);
}
+
+// The minimum bounding box for the entity.
+AABox EntityItemProperties::getAABoxInMeters() const {
+
+ // _position represents the position of the registration point.
+ glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint;
+
+ glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint);
+ glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder;
+ Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
+ Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation());
+
+ // shift the extents to be relative to the position/registration point
+ rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position);
+
+ return AABox(rotatedExtentsRelativeToRegistrationPoint);
+}
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index 3c77b63ab6..6e1594fb9b 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -101,6 +101,7 @@ public:
AACube getMaximumAACubeInTreeUnits() const;
AACube getMaximumAACubeInMeters() const;
+ AABox getAABoxInMeters() const;
void debugDump() const;
diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp
index 689ce2c747..f88df3b7c0 100644
--- a/libraries/script-engine/src/Vec3.cpp
+++ b/libraries/script-engine/src/Vec3.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include
+
#include
#include "Vec3.h"
@@ -37,7 +39,7 @@ 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;
+ return v1 - v2;
}
float Vec3::length(const glm::vec3& v) {
@@ -48,6 +50,10 @@ float Vec3::distance(const glm::vec3& v1, const glm::vec3& v2) {
return glm::distance(v1, v2);
}
+float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) {
+ return glm::degrees(glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3)));
+}
+
glm::vec3 Vec3::normalize(const glm::vec3& v) {
return glm::normalize(v);
}
diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h
index 2af1350e4a..693fd604f7 100644
--- a/libraries/script-engine/src/Vec3.h
+++ b/libraries/script-engine/src/Vec3.h
@@ -34,6 +34,7 @@ public slots:
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2);
float length(const glm::vec3& v);
float distance(const glm::vec3& v1, const glm::vec3& v2);
+ float orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3);
glm::vec3 normalize(const glm::vec3& v);
void print(const QString& lable, const glm::vec3& v);
bool equal(const glm::vec3& v1, const glm::vec3& v2);