diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html
index 0c2e09ef16..a1f1a5ea79 100644
--- a/examples/html/entityProperties.html
+++ b/examples/html/entityProperties.html
@@ -330,6 +330,8 @@
elLightSections[i].style.display = 'block';
}
+ elLightSpotLight.checked = properties.isSpotlight;
+
elLightColorRed.value = properties.color.red;
elLightColorGreen.value = properties.color.green;
elLightColorBlue.value = properties.color.blue;
diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js
index b6c536f063..70af987243 100644
--- a/examples/libraries/entitySelectionTool.js
+++ b/examples/libraries/entitySelectionTool.js
@@ -301,6 +301,8 @@ SelectionDisplay = (function () {
var grabberSolid = true;
var grabberMoveUpPosition = { x: 0, y: 0, z: 0 };
+ var lightOverlayColor = { red: 255, green: 153, blue: 0 };
+
var grabberPropertiesCorner = {
position: { x:0, y: 0, z: 0},
size: grabberSizeCorner,
@@ -340,6 +342,11 @@ SelectionDisplay = (function () {
borderSize: 1.4,
};
+ var spotLightLineProperties = {
+ color: lightOverlayColor,
+ lineWidth: 1.5,
+ };
+
var highlightBox = Overlays.addOverlay("cube", {
position: { x:0, y: 0, z: 0},
size: 1,
@@ -434,6 +441,44 @@ SelectionDisplay = (function () {
var grabberEdgeFR = Overlays.addOverlay("cube", grabberPropertiesEdge);
var grabberEdgeFL = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberSpotLightCircle = Overlays.addOverlay("circle3d", {
+ color: lightOverlayColor,
+ isSolid: false
+ });
+ var grabberSpotLightLineT = Overlays.addOverlay("line3d", spotLightLineProperties);
+ var grabberSpotLightLineB = Overlays.addOverlay("line3d", spotLightLineProperties);
+ var grabberSpotLightLineL = Overlays.addOverlay("line3d", spotLightLineProperties);
+ var grabberSpotLightLineR = Overlays.addOverlay("line3d", spotLightLineProperties);
+
+ var grabberSpotLightCenter = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberSpotLightRadius = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberSpotLightL = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberSpotLightR = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberSpotLightT = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberSpotLightB = Overlays.addOverlay("cube", grabberPropertiesEdge);
+
+ var grabberPointLightCircleX = Overlays.addOverlay("circle3d", {
+ rotation: Quat.fromPitchYawRollDegrees(0, 90, 0),
+ color: lightOverlayColor,
+ isSolid: false
+ });
+ var grabberPointLightCircleY = Overlays.addOverlay("circle3d", {
+ rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
+ color: lightOverlayColor,
+ isSolid: false
+ });
+ var grabberPointLightCircleZ = Overlays.addOverlay("circle3d", {
+ rotation: Quat.fromPitchYawRollDegrees(0, 0, 0),
+ color: lightOverlayColor,
+ isSolid: false
+ });
+ var grabberPointLightT = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberPointLightB = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberPointLightL = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberPointLightR = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberPointLightF = Overlays.addOverlay("cube", grabberPropertiesEdge);
+ var grabberPointLightN = Overlays.addOverlay("cube", grabberPropertiesEdge);
+
var stretchHandles = [
grabberLBN,
grabberRBN,
@@ -461,6 +506,25 @@ SelectionDisplay = (function () {
grabberEdgeNL,
grabberEdgeFR,
grabberEdgeFL,
+
+ grabberSpotLightLineT,
+ grabberSpotLightLineB,
+ grabberSpotLightLineL,
+ grabberSpotLightLineR,
+
+ grabberSpotLightCenter,
+ grabberSpotLightRadius,
+ grabberSpotLightL,
+ grabberSpotLightR,
+ grabberSpotLightT,
+ grabberSpotLightB,
+
+ grabberPointLightT,
+ grabberPointLightB,
+ grabberPointLightL,
+ grabberPointLightR,
+ grabberPointLightF,
+ grabberPointLightN,
];
@@ -648,6 +712,10 @@ SelectionDisplay = (function () {
yRailOverlay,
zRailOverlay,
baseOfEntityProjectionOverlay,
+ grabberSpotLightCircle,
+ grabberPointLightCircleX,
+ grabberPointLightCircleY,
+ grabberPointLightCircleZ,
].concat(stretchHandles);
overlayNames[highlightBox] = "highlightBox";
@@ -942,13 +1010,20 @@ SelectionDisplay = (function () {
var translateHandlesVisible = true;
var stretchHandlesVisible = true;
var selectionBoxVisible = true;
+ var isPointLight = false;
+
+ if (selectionManager.selections.length == 1) {
+ var properties = Entities.getEntityProperties(selectionManager.selections[0]);
+ isPointLight = properties.type == "Light" && !properties.isSpotlight;
+ }
+
if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_X in case they Z") {
rotationOverlaysVisible = true;
rotateHandlesVisible = false;
translateHandlesVisible = false;
stretchHandlesVisible = false;
selectionBoxVisible = false;
- } else if (mode == "TRANSLATE_UP_DOWN") {
+ } else if (mode == "TRANSLATE_UP_DOWN" || isPointLight) {
rotateHandlesVisible = false;
stretchHandlesVisible = false;
} else if (mode != "UNKNOWN") {
@@ -1122,6 +1197,171 @@ SelectionDisplay = (function () {
var stretchHandlesVisible = spaceMode == SPACE_LOCAL;
var extendedStretchHandlesVisible = stretchHandlesVisible && showExtendedStretchHandles;
+
+ if (selectionManager.selections.length == 1 ) {
+ var properties = Entities.getEntityProperties(selectionManager.selections[0]);
+ if (properties.type == "Light" && properties.isSpotlight == true) {
+ var stretchHandlesVisible = false;
+ var extendedStretchHandlesVisible = false;
+
+ Overlays.editOverlay(grabberSpotLightCenter, {
+ position: position,
+ visible: false,
+ });
+ Overlays.editOverlay(grabberSpotLightRadius, {
+ position: NEAR,
+ rotation: rotation,
+ visible: true,
+ });
+ var distance = (properties.dimensions.z / 2) * Math.tan(properties.cutoff * (Math.PI / 180));
+
+ Overlays.editOverlay(grabberSpotLightL, {
+ position: EdgeNL,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberSpotLightR, {
+ position: EdgeNR,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberSpotLightT, {
+ position: EdgeTN,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberSpotLightB, {
+ position: EdgeBN,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberSpotLightCircle, {
+ position: NEAR,
+ dimensions: { x: distance * 2, y: distance * 2, z: 1 },
+ lineWidth: 1.5,
+ rotation: rotation,
+ visible: true,
+ });
+
+ Overlays.editOverlay(grabberSpotLightLineT, {
+ start: position,
+ end: EdgeTN,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberSpotLightLineB, {
+ start: position,
+ end: EdgeBN,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberSpotLightLineR, {
+ start: position,
+ end: EdgeNR,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberSpotLightLineL, {
+ start: position,
+ end: EdgeNL,
+ visible: true,
+ });
+
+ Overlays.editOverlay(grabberPointLightCircleX, { visible: false });
+ Overlays.editOverlay(grabberPointLightCircleY, { visible: false });
+ Overlays.editOverlay(grabberPointLightCircleZ, { visible: false });
+ Overlays.editOverlay(grabberPointLightT, { visible: false });
+ Overlays.editOverlay(grabberPointLightB, { visible: false });
+ Overlays.editOverlay(grabberPointLightL, { visible: false });
+ Overlays.editOverlay(grabberPointLightR, { visible: false });
+ Overlays.editOverlay(grabberPointLightF, { visible: false });
+ Overlays.editOverlay(grabberPointLightN, { visible: false });
+ } else if (properties.type == "Light" && properties.isSpotlight == false) {
+ var stretchHandlesVisible = false;
+ var extendedStretchHandlesVisible = false;
+ Overlays.editOverlay(grabberPointLightT, {
+ position: TOP,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightB, {
+ position: BOTTOM,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightL, {
+ position: LEFT,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightR, {
+ position: RIGHT,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightF, {
+ position: FAR,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightN, {
+ position: NEAR,
+ rotation: rotation,
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightCircleX, {
+ position: position,
+ rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)),
+ dimensions: { x: properties.dimensions.z, y: properties.dimensions.z, z: 1 },
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightCircleY, {
+ position: position,
+ rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)),
+ dimensions: { x: properties.dimensions.z, y: properties.dimensions.z, z: 1 },
+ visible: true,
+ });
+ Overlays.editOverlay(grabberPointLightCircleZ, {
+ position: position,
+ rotation: rotation,
+ dimensions: { x: properties.dimensions.z, y: properties.dimensions.z, z: 1 },
+ visible: true,
+ });
+
+ Overlays.editOverlay(grabberSpotLightRadius, { visible: false });
+ Overlays.editOverlay(grabberSpotLightL, { visible: false });
+ Overlays.editOverlay(grabberSpotLightR, { visible: false });
+ Overlays.editOverlay(grabberSpotLightT, { visible: false });
+ Overlays.editOverlay(grabberSpotLightB, { visible: false });
+ Overlays.editOverlay(grabberSpotLightCircle, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineL, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineR, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineT, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineB, { visible: false });
+ } else {
+ Overlays.editOverlay(grabberSpotLightCenter, { visible: false });
+ Overlays.editOverlay(grabberSpotLightRadius, { visible: false });
+ Overlays.editOverlay(grabberSpotLightL, { visible: false });
+ Overlays.editOverlay(grabberSpotLightR, { visible: false });
+ Overlays.editOverlay(grabberSpotLightT, { visible: false });
+ Overlays.editOverlay(grabberSpotLightB, { visible: false });
+ Overlays.editOverlay(grabberSpotLightCircle, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineL, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineR, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineT, { visible: false });
+ Overlays.editOverlay(grabberSpotLightLineB, { visible: false });
+
+ Overlays.editOverlay(grabberPointLightCircleX, { visible: false });
+ Overlays.editOverlay(grabberPointLightCircleY, { visible: false });
+ Overlays.editOverlay(grabberPointLightCircleZ, { visible: false });
+ Overlays.editOverlay(grabberPointLightT, { visible: false });
+ Overlays.editOverlay(grabberPointLightB, { visible: false });
+ Overlays.editOverlay(grabberPointLightL, { visible: false });
+ Overlays.editOverlay(grabberPointLightR, { visible: false });
+ Overlays.editOverlay(grabberPointLightF, { visible: false });
+ Overlays.editOverlay(grabberPointLightN, { visible: false });
+ }
+ }
+
+
+
Overlays.editOverlay(grabberLBN, { visible: stretchHandlesVisible, rotation: rotation, position: LBN });
Overlays.editOverlay(grabberRBN, { visible: stretchHandlesVisible, rotation: rotation, position: RBN });
Overlays.editOverlay(grabberLBF, { visible: stretchHandlesVisible, rotation: rotation, position: LBF });
@@ -1422,7 +1662,7 @@ SelectionDisplay = (function () {
// direction - direction to stretch in
// pivot - point to use as a pivot
// offset - the position of the overlay tool relative to the selections center position
- var makeStretchTool = function(stretchMode, direction, pivot, offset) {
+ var makeStretchTool = function(stretchMode, direction, pivot, offset, customOnMove) {
var signs = {
x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0),
y: direction.y < 0 ? -1 : (direction.y > 0 ? 1 : 0),
@@ -1554,7 +1794,7 @@ SelectionDisplay = (function () {
};
var onMove = function(event) {
- var proportional = spaceMode == SPACE_WORLD || event.isShifted;
+ var proportional = spaceMode == SPACE_WORLD || event.isShifted || activeTool.mode == "STRETCH_RADIUS";
var position, dimensions, rotation;
if (spaceMode == SPACE_LOCAL) {
@@ -1577,61 +1817,66 @@ SelectionDisplay = (function () {
vector = vec3Mult(mask, vector);
- vector = grid.snapToSpacing(vector);
-
- var changeInDimensions = Vec3.multiply(-1, vec3Mult(signs, vector));
- var newDimensions;
- if (proportional) {
- var absX = Math.abs(changeInDimensions.x);
- var absY = Math.abs(changeInDimensions.y);
- var absZ = Math.abs(changeInDimensions.z);
- var pctChange = 0;
- if (absX > absY && absX > absZ) {
- pctChange = changeInDimensions.x / initialProperties.dimensions.x;
- pctChange = changeInDimensions.x / initialDimensions.x;
- } else if (absY > absZ) {
- pctChange = changeInDimensions.y / initialProperties.dimensions.y;
- pctChange = changeInDimensions.y / initialDimensions.y;
- } else {
- pctChange = changeInDimensions.z / initialProperties.dimensions.z;
- pctChange = changeInDimensions.z / initialDimensions.z;
- }
- pctChange += 1.0;
- newDimensions = Vec3.multiply(pctChange, initialDimensions);
+ if (customOnMove) {
+ var change = Vec3.multiply(-1, vec3Mult(signs, vector));
+ customOnMove(vector, change);
} else {
- newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
- }
-
- newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION);
- newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION);
- newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION);
-
- var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(deltaPivot, changeInDimensions));
- var newPosition = Vec3.sum(initialPosition, changeInPosition);
-
- for (var i = 0; i < SelectionManager.selections.length; i++) {
- Entities.editEntity(SelectionManager.selections[i], {
- position: newPosition,
- dimensions: newDimensions,
- });
- }
+ vector = grid.snapToSpacing(vector);
- var wantDebug = false;
- if (wantDebug) {
- print(stretchMode);
- Vec3.print(" newIntersection:", newIntersection);
- Vec3.print(" vector:", vector);
- Vec3.print(" oldPOS:", oldPOS);
- Vec3.print(" newPOS:", newPOS);
- Vec3.print(" changeInDimensions:", changeInDimensions);
- Vec3.print(" newDimensions:", newDimensions);
+ var changeInDimensions = Vec3.multiply(-1, vec3Mult(signs, vector));
+ var newDimensions;
+ if (proportional) {
+ var absX = Math.abs(changeInDimensions.x);
+ var absY = Math.abs(changeInDimensions.y);
+ var absZ = Math.abs(changeInDimensions.z);
+ var pctChange = 0;
+ if (absX > absY && absX > absZ) {
+ pctChange = changeInDimensions.x / initialProperties.dimensions.x;
+ pctChange = changeInDimensions.x / initialDimensions.x;
+ } else if (absY > absZ) {
+ pctChange = changeInDimensions.y / initialProperties.dimensions.y;
+ pctChange = changeInDimensions.y / initialDimensions.y;
+ } else {
+ pctChange = changeInDimensions.z / initialProperties.dimensions.z;
+ pctChange = changeInDimensions.z / initialDimensions.z;
+ }
+ pctChange += 1.0;
+ newDimensions = Vec3.multiply(pctChange, initialDimensions);
+ } else {
+ newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
+ }
+
+ newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION);
+ newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION);
+ newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION);
+
+ var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(deltaPivot, changeInDimensions));
+ var newPosition = Vec3.sum(initialPosition, changeInPosition);
+
+ for (var i = 0; i < SelectionManager.selections.length; i++) {
+ Entities.editEntity(SelectionManager.selections[i], {
+ position: newPosition,
+ dimensions: newDimensions,
+ });
+ }
- Vec3.print(" changeInPosition:", changeInPosition);
- Vec3.print(" newPosition:", newPosition);
+ var wantDebug = false;
+ if (wantDebug) {
+ print(stretchMode);
+ Vec3.print(" newIntersection:", newIntersection);
+ Vec3.print(" vector:", vector);
+ Vec3.print(" oldPOS:", oldPOS);
+ Vec3.print(" newPOS:", newPOS);
+ Vec3.print(" changeInDimensions:", changeInDimensions);
+ Vec3.print(" newDimensions:", newDimensions);
+
+ Vec3.print(" changeInPosition:", changeInPosition);
+ Vec3.print(" newPosition:", newPosition);
+ }
+
+ SelectionManager._update();
}
- SelectionManager._update();
-
};
return {
@@ -1642,15 +1887,57 @@ SelectionDisplay = (function () {
};
};
- function addStretchTool(overlay, mode, pivot, direction, offset) {
+ function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) {
if (!pivot) {
pivot = direction;
}
- var tool = makeStretchTool(mode, direction, pivot, offset);
+ var tool = makeStretchTool(mode, direction, pivot, offset, handleMove);
addGrabberTool(overlay, tool);
}
+ function cutoffStretchFunc(vector, change) {
+ vector = change;
+ Vec3.print("Radius stretch: ", vector);
+ var length = vector.x + vector.y + vector.z;
+ var props = selectionManager.savedProperties[selectionManager.selections[0].id];
+
+ var radius = props.dimensions.z / 2;
+ var originalCutoff = props.cutoff;
+
+ var originalSize = radius * Math.tan(originalCutoff * (Math.PI / 180));
+ var newSize = originalSize + length;
+ var cutoff = Math.atan2(newSize, radius) * 180 / Math.PI;
+
+ Entities.editEntity(selectionManager.selections[0], {
+ cutoff: cutoff,
+ });
+
+ SelectionManager._update();
+ };
+
+ function radiusStretchFunc(vector, change) {
+ var props = selectionManager.savedProperties[selectionManager.selections[0].id];
+
+ // Find the axis being adjusted
+ var size;
+ if (Math.abs(change.x) > 0) {
+ size = props.dimensions.x + change.x;
+ } else if (Math.abs(change.y) > 0) {
+ size = props.dimensions.y + change.y;
+ } else if (Math.abs(change.z) > 0) {
+ size = props.dimensions.z + change.z;
+ }
+
+ var newDimensions = { x: size, y: size, z: size };
+
+ Entities.editEntity(selectionManager.selections[0], {
+ dimensions: newDimensions,
+ });
+
+ SelectionManager._update();
+ }
+
addStretchTool(grabberNEAR, "STRETCH_NEAR", { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 });
addStretchTool(grabberFAR, "STRETCH_FAR", { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: 1 });
addStretchTool(grabberTOP, "STRETCH_TOP", { x: 0, y: -1, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: 1, z: 0 });
@@ -1658,6 +1945,19 @@ SelectionDisplay = (function () {
addStretchTool(grabberRIGHT, "STRETCH_RIGHT", { x: -1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 });
addStretchTool(grabberLEFT, "STRETCH_LEFT", { x: 1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 });
+ addStretchTool(grabberSpotLightRadius, "STRETCH_RADIUS", { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 });
+ addStretchTool(grabberSpotLightT, "STRETCH_CUTOFF_T", { x: 0, y: 0, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: 1, z: 0 }, cutoffStretchFunc);
+ addStretchTool(grabberSpotLightB, "STRETCH_CUTOFF_B", { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: -1, z: 0 }, cutoffStretchFunc);
+ addStretchTool(grabberSpotLightL, "STRETCH_CUTOFF_L", { x: 0, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, cutoffStretchFunc);
+ addStretchTool(grabberSpotLightR, "STRETCH_CUTOFF_R", { x: 0, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, cutoffStretchFunc);
+
+ addStretchTool(grabberPointLightT, "STRETCH_RADIUS_T", { x: 0, y: 0, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc);
+ addStretchTool(grabberPointLightB, "STRETCH_RADIUS_B", { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc);
+ addStretchTool(grabberPointLightL, "STRETCH_RADIUS_L", { x: 0, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc);
+ addStretchTool(grabberPointLightR, "STRETCH_RADIUS_R", { x: 0, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc);
+ addStretchTool(grabberPointLightF, "STRETCH_RADIUS_F", { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc);
+ addStretchTool(grabberPointLightN, "STRETCH_RADIUS_N", { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 }, radiusStretchFunc);
+
addStretchTool(grabberLBN, "STRETCH_LBN", null, {x: 1, y: 0, z: 1}, { x: -1, y: -1, z: -1 });
addStretchTool(grabberRBN, "STRETCH_RBN", null, {x: -1, y: 0, z: 1}, { x: 1, y: -1, z: -1 });
addStretchTool(grabberLBF, "STRETCH_LBF", null, {x: 1, y: 0, z: -1}, { x: -1, y: -1, z: 1 });
@@ -2399,6 +2699,17 @@ SelectionDisplay = (function () {
case grabberEdgeNL:
case grabberEdgeFR:
case grabberEdgeFL:
+ case grabberSpotLightRadius:
+ case grabberSpotLightT:
+ case grabberSpotLightB:
+ case grabberSpotLightL:
+ case grabberSpotLightR:
+ case grabberPointLightT:
+ case grabberPointLightB:
+ case grabberPointLightR:
+ case grabberPointLightL:
+ case grabberPointLightN:
+ case grabberPointLightF:
pickedColor = grabberColorEdge;
pickedAlpha = grabberAlpha;
highlightNeeded = true;
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 4bf9b791fa..51d33c2522 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -976,6 +976,11 @@ void Application::keyPressEvent(QKeyEvent* event) {
_myAvatar->setDriveKeys(UP, 1.0f);
break;
+ case Qt::Key_F: {
+ _physicsEngine.dumpNextStats();
+ break;
+ }
+
case Qt::Key_Asterisk:
Menu::getInstance()->triggerOption(MenuOption::Stars);
break;
@@ -1183,6 +1188,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_Comma: {
renderCollisionHulls = !renderCollisionHulls;
+ break;
}
default:
diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h
index 6c23776e18..fe2da31231 100644
--- a/interface/src/devices/OculusManager.h
+++ b/interface/src/devices/OculusManager.h
@@ -31,6 +31,10 @@ class Text3DOverlay;
#define OVR_CLIENT_DISTORTION 1
+// Direct HMD mode is currently only supported on windows and some linux systems will
+// misbehave if we try to enable the Oculus SDK at all, so isolate support for Direct
+// mode only to windows for now
+#ifdef Q_OS_WIN
// On Win32 platforms, enabling Direct HMD requires that the SDK be
// initialized before the GL context is set up, but this breaks v-sync
// for any application that has a Direct mode enable Rift connected
@@ -40,6 +44,7 @@ class Text3DOverlay;
// caveat that it will break v-sync in NON-VR mode if you have an Oculus
// Rift connect and in Direct mode
#define OVR_DIRECT_MODE 1
+#endif
/// Handles interaction with the Oculus Rift.
diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp
index 132f58b6f0..0e20fd8ce4 100644
--- a/interface/src/ui/overlays/Circle3DOverlay.cpp
+++ b/interface/src/ui/overlays/Circle3DOverlay.cpp
@@ -98,6 +98,9 @@ void Circle3DOverlay::render(RenderArgs* args) {
const float MAX_COLOR = 255.0f;
glm::vec4 color(colorX.red / MAX_COLOR, colorX.green / MAX_COLOR, colorX.blue / MAX_COLOR, alpha);
+ bool colorChanged = colorX.red != _lastColor.red || colorX.green != _lastColor.green || colorX.blue != _lastColor.blue;
+ _lastColor = colorX;
+
glDisable(GL_LIGHTING);
glm::vec3 position = getPosition();
@@ -131,7 +134,7 @@ void Circle3DOverlay::render(RenderArgs* args) {
_quadVerticesID = geometryCache->allocateID();
}
- if (geometryChanged) {
+ if (geometryChanged || colorChanged) {
QVector points;
@@ -170,7 +173,7 @@ void Circle3DOverlay::render(RenderArgs* args) {
_lineVerticesID = geometryCache->allocateID();
}
- if (geometryChanged) {
+ if (geometryChanged || colorChanged) {
QVector points;
float angle = startAt;
diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h
index 10b7a5dbfa..fa9ecd0f25 100644
--- a/interface/src/ui/overlays/Circle3DOverlay.h
+++ b/interface/src/ui/overlays/Circle3DOverlay.h
@@ -70,6 +70,7 @@ protected:
int _majorTicksVerticesID;
int _minorTicksVerticesID;
+ xColor _lastColor;
float _lastStartAt;
float _lastEndAt;
float _lastOuterRadius;
diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp
index c7a4ef79d7..996677141a 100644
--- a/libraries/entities/src/LightEntityItem.cpp
+++ b/libraries/entities/src/LightEntityItem.cpp
@@ -41,8 +41,16 @@ LightEntityItem::LightEntityItem(const EntityItemID& entityItemID, const EntityI
}
void LightEntityItem::setDimensions(const glm::vec3& value) {
- float maxDimension = glm::max(value.x, value.y, value.z);
- _dimensions = glm::vec3(maxDimension, maxDimension, maxDimension);
+ if (_isSpotlight) {
+ // If we are a spotlight, treat the z value as our radius or length, and
+ // recalculate the x/y dimensions to properly encapsulate the spotlight.
+ const float length = value.z;
+ const float width = length * glm::tan(glm::radians(_cutoff));
+ _dimensions = glm::vec3(width, width, length);
+ } else {
+ float maxDimension = glm::max(value.x, value.y, value.z);
+ _dimensions = glm::vec3(maxDimension, maxDimension, maxDimension);
+ }
}
@@ -58,6 +66,33 @@ EntityItemProperties LightEntityItem::getProperties() const {
return properties;
}
+void LightEntityItem::setIsSpotlight(bool value) {
+ if (value != _isSpotlight) {
+ _isSpotlight = value;
+
+ if (_isSpotlight) {
+ const float length = _dimensions.z;
+ const float width = length * glm::tan(glm::radians(_cutoff));
+ _dimensions = glm::vec3(width, width, length);
+ } else {
+ float maxDimension = glm::max(_dimensions.x, _dimensions.y, _dimensions.z);
+ _dimensions = glm::vec3(maxDimension, maxDimension, maxDimension);
+ }
+ }
+}
+
+void LightEntityItem::setCutoff(float value) {
+ _cutoff = glm::clamp(value, 0.0f, 90.0f);
+
+ if (_isSpotlight) {
+ // If we are a spotlight, adjusting the cutoff will affect the area we encapsulate,
+ // so update the dimensions to reflect this.
+ const float length = _dimensions.z;
+ const float width = length * glm::tan(glm::radians(_cutoff));
+ _dimensions = glm::vec3(width, width, length);
+ }
+}
+
bool LightEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h
index cdbdb59ece..373f88286e 100644
--- a/libraries/entities/src/LightEntityItem.h
+++ b/libraries/entities/src/LightEntityItem.h
@@ -56,7 +56,7 @@ public:
}
bool getIsSpotlight() const { return _isSpotlight; }
- void setIsSpotlight(bool value) { _isSpotlight = value; }
+ void setIsSpotlight(bool value);
float getIntensity() const { return _intensity; }
void setIntensity(float value) { _intensity = value; }
@@ -65,7 +65,7 @@ public:
void setExponent(float value) { _exponent = value; }
float getCutoff() const { return _cutoff; }
- void setCutoff(float value) { _cutoff = value; }
+ void setCutoff(float value);
static bool getLightsArePickable() { return _lightsArePickable; }
static void setLightsArePickable(bool value) { _lightsArePickable = value; }
diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp
index cdff02820d..1d7a84c177 100755
--- a/libraries/physics/src/CharacterController.cpp
+++ b/libraries/physics/src/CharacterController.cpp
@@ -257,6 +257,7 @@ btPairCachingGhostObject* CharacterController::getGhostObject() {
}
bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorld) {
+ BT_PROFILE("recoverFromPenetration");
// Here we must refresh the overlapping paircache as the penetrating movement itself or the
// previous recovery iteration might have used setWorldTransform and pushed us into an object
// that is not in the previous cache contents from the last timestep, as will happen if we
@@ -355,6 +356,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
void CharacterController::scanDown(btCollisionWorld* world) {
+ BT_PROFILE("scanDown");
// we test with downward raycast and if we don't find floor close enough then turn on "hover"
btKinematicClosestNotMeRayResultCallback callback(_ghostObject);
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
@@ -374,6 +376,7 @@ void CharacterController::scanDown(btCollisionWorld* world) {
}
void CharacterController::stepUp(btCollisionWorld* world) {
+ BT_PROFILE("stepUp");
// phase 1: up
// compute start and end
@@ -440,6 +443,7 @@ void CharacterController::updateTargetPositionBasedOnCollision(const btVector3&
}
void CharacterController::stepForward(btCollisionWorld* collisionWorld, const btVector3& movement) {
+ BT_PROFILE("stepForward");
// phase 2: forward
_targetPosition = _currentPosition + movement;
@@ -496,6 +500,7 @@ void CharacterController::stepForward(btCollisionWorld* collisionWorld, const bt
}
void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt) {
+ BT_PROFILE("stepDown");
// phase 3: down
//
// The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase.
@@ -607,6 +612,7 @@ void CharacterController::warp(const btVector3& origin) {
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
+ BT_PROFILE("preStep");
if (!_enabled) {
return;
}
@@ -627,6 +633,7 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) {
}
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
+ BT_PROFILE("playerStep");
if (!_enabled) {
return; // no motion
}
@@ -875,6 +882,7 @@ void CharacterController::updateShapeIfNecessary() {
}
void CharacterController::preSimulation(btScalar timeStep) {
+ BT_PROFILE("preSimulation");
if (_enabled && _dynamicsWorld) {
glm::quat rotation = _avatarData->getOrientation();
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
@@ -897,6 +905,7 @@ void CharacterController::preSimulation(btScalar timeStep) {
}
void CharacterController::postSimulation() {
+ BT_PROFILE("postSimulation");
if (_enabled && _ghostObject) {
const btTransform& avatarTransform = _ghostObject->getWorldTransform();
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp
index 467b51560f..67e4e0616f 100644
--- a/libraries/physics/src/PhysicsEngine.cpp
+++ b/libraries/physics/src/PhysicsEngine.cpp
@@ -155,6 +155,7 @@ void PhysicsEngine::clearEntitiesInternal() {
// end EntitySimulation overrides
void PhysicsEngine::relayIncomingChangesToSimulation() {
+ BT_PROFILE("incomingChanges");
// process incoming changes
QSet::iterator stateItr = _incomingChanges.begin();
while (stateItr != _incomingChanges.end()) {
@@ -287,66 +288,76 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
}
void PhysicsEngine::stepSimulation() {
- lock();
- // NOTE: the grand order of operations is:
- // (1) pull incoming changes
- // (2) step simulation
- // (3) synchronize outgoing motion states
- // (4) send outgoing packets
-
- // This is step (1) pull incoming changes
- relayIncomingChangesToSimulation();
-
- const int MAX_NUM_SUBSTEPS = 4;
- const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP;
- float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds());
- _clock.reset();
- float timeStep = btMin(dt, MAX_TIMESTEP);
-
- // TODO: move character->preSimulation() into relayIncomingChanges
- if (_characterController) {
- if (_characterController->needsRemoval()) {
- _characterController->setDynamicsWorld(NULL);
- }
- _characterController->updateShapeIfNecessary();
- if (_characterController->needsAddition()) {
- _characterController->setDynamicsWorld(_dynamicsWorld);
- }
- _characterController->preSimulation(timeStep);
- }
-
- // This is step (2) step simulation
- int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
- _numSubsteps += (uint32_t)numSubsteps;
- stepNonPhysicalKinematics(usecTimestampNow());
- unlock();
-
- // TODO: make all of this harvest stuff into one function: relayOutgoingChanges()
- if (numSubsteps > 0) {
- // This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
- //
- // Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
- // to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
- // PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
- // lock on the tree before we re-lock ourselves.
- //
- // TODO: untangle these lock sequences.
- _entityTree->lockForWrite();
+ {
lock();
- _dynamicsWorld->synchronizeMotionStates();
+ CProfileManager::Reset();
+ BT_PROFILE("stepSimulation");
+ // NOTE: the grand order of operations is:
+ // (1) pull incoming changes
+ // (2) step simulation
+ // (3) synchronize outgoing motion states
+ // (4) send outgoing packets
- if (_characterController) {
- _characterController->postSimulation();
- }
-
- unlock();
- _entityTree->unlock();
+ // This is step (1) pull incoming changes
+ relayIncomingChangesToSimulation();
- computeCollisionEvents();
+ const int MAX_NUM_SUBSTEPS = 4;
+ const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP;
+ float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds());
+ _clock.reset();
+ float timeStep = btMin(dt, MAX_TIMESTEP);
+
+ // TODO: move character->preSimulation() into relayIncomingChanges
+ if (_characterController) {
+ if (_characterController->needsRemoval()) {
+ _characterController->setDynamicsWorld(NULL);
+ }
+ _characterController->updateShapeIfNecessary();
+ if (_characterController->needsAddition()) {
+ _characterController->setDynamicsWorld(_dynamicsWorld);
+ }
+ _characterController->preSimulation(timeStep);
+ }
+
+ // This is step (2) step simulation
+ int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
+ _numSubsteps += (uint32_t)numSubsteps;
+ stepNonPhysicalKinematics(usecTimestampNow());
+ unlock();
+
+ // TODO: make all of this harvest stuff into one function: relayOutgoingChanges()
+ if (numSubsteps > 0) {
+ BT_PROFILE("postSimulation");
+ // This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
+ //
+ // Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
+ // to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
+ // PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
+ // lock on the tree before we re-lock ourselves.
+ //
+ // TODO: untangle these lock sequences.
+ _entityTree->lockForWrite();
+ lock();
+ _dynamicsWorld->synchronizeMotionStates();
+
+ if (_characterController) {
+ _characterController->postSimulation();
+ }
+
+ unlock();
+ _entityTree->unlock();
+
+ computeCollisionEvents();
+ }
+ }
+ if (_dumpNextStats) {
+ _dumpNextStats = false;
+ CProfileManager::dumpAll();
}
}
void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) {
+ BT_PROFILE("nonPhysicalKinematics");
QSet::iterator stateItr = _nonPhysicalKinematicObjects.begin();
while (stateItr != _nonPhysicalKinematicObjects.end()) {
ObjectMotionState* motionState = *stateItr;
@@ -358,6 +369,7 @@ void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) {
// TODO?: need to occasionally scan for stopped non-physical kinematics objects
void PhysicsEngine::computeCollisionEvents() {
+ BT_PROFILE("computeCollisionEvents");
// update all contacts every frame
int numManifolds = _collisionDispatcher->getNumManifolds();
for (int i = 0; i < numManifolds; ++i) {
diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h
index 0661b47d3a..d7d3278286 100644
--- a/libraries/physics/src/PhysicsEngine.h
+++ b/libraries/physics/src/PhysicsEngine.h
@@ -86,6 +86,8 @@ public:
void setCharacterController(CharacterController* character);
+ void dumpNextStats() { _dumpNextStats = true; }
+
private:
/// \param motionState pointer to Object's MotionState
void removeObjectFromBullet(ObjectMotionState* motionState);
@@ -121,6 +123,8 @@ private:
/// character collisions
CharacterController* _characterController = NULL;
+
+ bool _dumpNextStats = false;
};
#endif // hifi_PhysicsEngine_h
diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp
index c7ee9ce2a0..a345b915db 100644
--- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp
+++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp
@@ -15,6 +15,8 @@
* Copied and modified from btDiscreteDynamicsWorld.cpp by AndrewMeadows on 2014.11.12.
* */
+#include
+
#include "ThreadSafeDynamicsWorld.h"
ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
@@ -25,50 +27,51 @@ ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
: btDiscreteDynamicsWorld(dispatcher, pairCache, constraintSolver, collisionConfiguration) {
}
-int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep) {
- int subSteps = 0;
- if (maxSubSteps) {
- //fixed timestep with interpolation
- m_fixedTimeStep = fixedTimeStep;
- m_localTime += timeStep;
- if (m_localTime >= fixedTimeStep)
- {
- subSteps = int( m_localTime / fixedTimeStep);
- m_localTime -= subSteps * fixedTimeStep;
- }
- } else {
- //variable timestep
- fixedTimeStep = timeStep;
- m_localTime = m_latencyMotionStateInterpolation ? 0 : timeStep;
- m_fixedTimeStep = 0;
- if (btFuzzyZero(timeStep))
- {
- subSteps = 0;
- maxSubSteps = 0;
- } else
- {
- subSteps = 1;
- maxSubSteps = 1;
- }
- }
+int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep) {
+ BT_PROFILE("stepSimulation");
+ int subSteps = 0;
+ if (maxSubSteps) {
+ //fixed timestep with interpolation
+ m_fixedTimeStep = fixedTimeStep;
+ m_localTime += timeStep;
+ if (m_localTime >= fixedTimeStep)
+ {
+ subSteps = int( m_localTime / fixedTimeStep);
+ m_localTime -= subSteps * fixedTimeStep;
+ }
+ } else {
+ //variable timestep
+ fixedTimeStep = timeStep;
+ m_localTime = m_latencyMotionStateInterpolation ? 0 : timeStep;
+ m_fixedTimeStep = 0;
+ if (btFuzzyZero(timeStep))
+ {
+ subSteps = 0;
+ maxSubSteps = 0;
+ } else
+ {
+ subSteps = 1;
+ maxSubSteps = 1;
+ }
+ }
- /*//process some debugging flags
- if (getDebugDrawer()) {
- btIDebugDraw* debugDrawer = getDebugDrawer ();
- gDisableDeactivation = (debugDrawer->getDebugMode() & btIDebugDraw::DBG_NoDeactivation) != 0;
- }*/
- if (subSteps) {
- //clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
- int clampedSimulationSteps = (subSteps > maxSubSteps)? maxSubSteps : subSteps;
+ /*//process some debugging flags
+ if (getDebugDrawer()) {
+ btIDebugDraw* debugDrawer = getDebugDrawer ();
+ gDisableDeactivation = (debugDrawer->getDebugMode() & btIDebugDraw::DBG_NoDeactivation) != 0;
+ }*/
+ if (subSteps) {
+ //clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
+ int clampedSimulationSteps = (subSteps > maxSubSteps)? maxSubSteps : subSteps;
- saveKinematicState(fixedTimeStep*clampedSimulationSteps);
+ saveKinematicState(fixedTimeStep*clampedSimulationSteps);
- applyGravity();
+ applyGravity();
- for (int i=0;i