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