diff --git a/cmake/macros/IncludeBullet.cmake b/cmake/macros/IncludeBullet.cmake new file mode 100644 index 0000000000..a238b33660 --- /dev/null +++ b/cmake/macros/IncludeBullet.cmake @@ -0,0 +1,18 @@ +# +# IncludeBullet.cmake +# +# 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 +# + +macro(INCLUDE_BULLET) + find_package(Bullet) + if (BULLET_FOUND) + include_directories("${BULLET_INCLUDE_DIRS}") + if (APPLE OR UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BULLET_PHYSICS -isystem ${BULLET_INCLUDE_DIRS}") + endif () + endif (BULLET_FOUND) +endmacro(INCLUDE_BULLET) diff --git a/examples/editModels.js b/examples/editModels.js index 491e6c9f33..8ec099650a 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1884,7 +1884,7 @@ function controller(wichSide) { this.jointsIntersectingFromStart = []; this.laser = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z: 0 }, + start: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, color: LASER_COLOR, alpha: 1, @@ -1904,7 +1904,7 @@ function controller(wichSide) { anchor: "MyAvatar" }); this.leftRight = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z: 0 }, + start: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, color: { red: 0, green: 0, blue: 255 }, alpha: 1, @@ -1913,7 +1913,7 @@ function controller(wichSide) { anchor: "MyAvatar" }); this.topDown = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z: 0 }, + start: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, color: { red: 0, green: 0, blue: 255 }, alpha: 1, @@ -2066,7 +2066,7 @@ function controller(wichSide) { var endPosition = Vec3.sum(startPosition, direction); Overlays.editOverlay(this.laser, { - position: startPosition, + start: startPosition, end: endPosition }); @@ -2075,10 +2075,11 @@ function controller(wichSide) { position: endPosition }); Overlays.editOverlay(this.leftRight, { - position: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)), + start: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)), end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)) }); - Overlays.editOverlay(this.topDown, { position: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), + Overlays.editOverlay(this.topDown, { + start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)) }); this.showLaser(!this.grabbing || mode == 0); diff --git a/examples/gamepad.js b/examples/gamepad.js index 6caa9c476c..c28747216d 100644 --- a/examples/gamepad.js +++ b/examples/gamepad.js @@ -75,7 +75,7 @@ var warpSphere = Overlays.addOverlay("sphere", { var WARP_LINE_HEIGHT = 10; var warpLine = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z:0 }, + start: { x: 0, y: 0, z:0 }, end: { x: 0, y: 0, z: 0 }, color: { red: 0, green: 255, blue: 255}, alpha: 1, @@ -131,7 +131,7 @@ function updateWarp() { visible: true, }); Overlays.editOverlay(warpLine, { - position: warpPosition, + start: warpPosition, end: Vec3.sum(warpPosition, { x: 0, y: WARP_LINE_HEIGHT, z: 0 }), visible: true, }); diff --git a/examples/headMove.js b/examples/headMove.js index 943664b70f..3152976383 100644 --- a/examples/headMove.js +++ b/examples/headMove.js @@ -36,7 +36,7 @@ var warpSphere = Overlays.addOverlay("sphere", { var WARP_LINE_HEIGHT = 5; var warpLine = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z:0 }, + start: { x: 0, y: 0, z:0 }, end: { x: 0, y: 0, z: 0 }, color: { red: 0, green: 255, blue: 255}, alpha: 1, @@ -120,7 +120,7 @@ function updateWarp() { visible: willMove, }); Overlays.editOverlay(warpLine, { - position: Vec3.sum(warpPosition, { x: 0, y: -WARP_LINE_HEIGHT / 2.0, z: 0 }), + start: Vec3.sum(warpPosition, { x: 0, y: -WARP_LINE_HEIGHT / 2.0, z: 0 }), end: Vec3.sum(warpPosition, { x: 0, y: WARP_LINE_HEIGHT / 2.0, z: 0 }), visible: willMove, }); diff --git a/examples/libraries/entityCameraTool.js b/examples/libraries/entityCameraTool.js index 803a58f48e..cd8aa3656b 100644 --- a/examples/libraries/entityCameraTool.js +++ b/examples/libraries/entityCameraTool.js @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("libraries/overlayUtils.js"); + var MOUSE_SENSITIVITY = 0.9; var SCROLL_SENSITIVITY = 0.05; var PAN_ZOOM_SCALE_RATIO = 0.4; @@ -41,6 +43,17 @@ var easeOutCubic = function(t) { EASE_TIME = 0.5; +function mergeObjects(obj1, obj2) { + var newObj = {}; + for (key in obj1) { + newObj[key] = obj1[key]; + } + for (key in obj2) { + newObj[key] = obj2[key]; + } + return newObj; +} + CameraManager = function() { var that = {}; @@ -92,8 +105,6 @@ CameraManager = function() { Camera.mode = "independent"; that.updateCamera(); - - cameraTool.setVisible(false); } that.disable = function(ignoreCamera) { @@ -104,7 +115,6 @@ CameraManager = function() { if (!ignoreCamera) { Camera.mode = that.previousCameraMode; } - cameraTool.setVisible(false); } that.focus = function(position, dimensions, easeOrientation) { @@ -140,6 +150,11 @@ CameraManager = function() { that.updateCamera(); } + that.setTargetPitchYaw = function(pitch, yaw) { + that.targetPitch = pitch; + that.targetYaw = yaw; + } + that.moveFocalPoint = function(dPos) { that.setFocalPoint(Vec3.sum(that.focalPoint, dPos)); } @@ -228,6 +243,10 @@ CameraManager = function() { } that.mousePressEvent = function(event) { + if (cameraTool.mousePressEvent(event)) { + return true; + } + if (!that.enabled) return; if (event.isRightButton || (event.isLeftButton && event.isControl && !event.isShifted)) { @@ -247,7 +266,7 @@ CameraManager = function() { return true; } - return cameraTool.mousePressEvent(event); + return false; } that.mouseReleaseEvent = function(event) { @@ -271,7 +290,10 @@ CameraManager = function() { } that.updateCamera = function() { - if (!that.enabled || Camera.mode != "independent") return; + if (!that.enabled || Camera.mode != "independent") { + cameraTool.update(); + return; + } var yRot = Quat.angleAxis(that.yaw, { x: 0, y: 1, z: 0 }); var xRot = Quat.angleAxis(that.pitch, { x: 1, y: 0, z: 0 }); @@ -290,6 +312,8 @@ CameraManager = function() { } Camera.setOrientation(q); + + cameraTool.update(); } function normalizeDegrees(degrees) { @@ -301,6 +325,7 @@ CameraManager = function() { // Ease the position and orbit of the camera that.update = function(dt) { if (Camera.mode != "independent") { + that.updateCamera(); return; } @@ -363,316 +388,210 @@ CameraManager = function() { return that; } -var ZoomTool = function(opts) { - var that = {}; - - var position = opts.position || { x: 0, y: 0 }; - var height = opts.height || 200; - var color = opts.color || { red: 255, green: 0, blue: 0 }; - var arrowButtonSize = opts.buttonSize || 20; - var arrowButtonBackground = opts.arrowBackground || { red: 255, green: 255, blue: 255 }; - var zoomBackground = { red: 128, green: 0, blue: 0 }; - var zoomHeight = height - (arrowButtonSize * 2); - var zoomBarY = position.y + arrowButtonSize, - - var onIncreasePressed = opts.onIncreasePressed; - var onDecreasePressed = opts.onDecreasePressed; - var onPercentageSet = opts.onPercentageSet; - - var increaseButton = Overlays.addOverlay("text", { - x: position.x, - y: position.y, - width: arrowButtonSize, - height: arrowButtonSize, - color: color, - backgroundColor: arrowButtonBackground, - topMargin: 4, - leftMargin: 4, - text: "+", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - var decreaseButton = Overlays.addOverlay("text", { - x: position.x, - y: position.y + arrowButtonSize + zoomHeight, - width: arrowButtonSize, - height: arrowButtonSize, - color: color, - backgroundColor: arrowButtonBackground, - topMargin: 4, - leftMargin: 4, - text: "-", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - var zoomBar = Overlays.addOverlay("text", { - x: position.x + 5, - y: zoomBarY, - width: 10, - height: zoomHeight, - color: { red: 0, green: 255, blue: 0 }, - backgroundColor: zoomBackground, - topMargin: 4, - leftMargin: 4, - text: "", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - var zoomHandle = Overlays.addOverlay("text", { - x: position.x, - y: position.y + arrowButtonSize, - width: arrowButtonSize, - height: 10, - backgroundColor: { red: 0, green: 255, blue: 0 }, - topMargin: 4, - leftMargin: 4, - text: "", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - - var allOverlays = [ - increaseButton, - decreaseButton, - zoomBar, - zoomHandle, - ]; - - that.destroy = function() { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.deleteOverlay(allOverlays[i]); - } - }; - - that.setVisible = function(visible) { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.editOverlay(allOverlays[i], { visible: visible }); - } - } - - that.setZoomPercentage = function(pct) { - var yOffset = (zoomHeight - 10) * pct; - Overlays.editOverlay(zoomHandle, { - y: position.y + arrowButtonSize + yOffset, - }); - } - - that.mouseReleaseEvent = function(event) { - var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - var clicked = false; - if (clickedOverlay == increaseButton) { - if (onIncreasePressed) onIncreasePressed(); - clicked = true; - } else if (clickedOverlay == decreaseButton) { - if (onDecreasePressed) onDecreasePressed(); - clicked = true; - } else if (clickedOverlay == zoomBar) { - if (onPercentageSet) onPercentageSet((event.y - zoomBarY) / zoomHeight); - clicked = true; - } - return clicked; - } - - return that; -}; - -var ArrowTool = function(opts) { - var that = {}; - - var position = opts.position || { x: 0, y: 0 }; - var arrowButtonSize = opts.buttonSize || 20; - var color = opts.color || { red: 255, green: 0, blue: 0 }; - var arrowButtonBackground = opts.arrowBackground || { red: 255, green: 255, blue: 255 }; - var centerButtonBackground = opts.centerBackground || { red: 255, green: 255, blue: 255 }; - var onUpPressed = opts.onUpPressed; - var onDownPressed = opts.onDownPressed; - var onLeftPressed = opts.onLeftPressed; - var onRightPressed = opts.onRightPressed; - var onCenterPressed = opts.onCenterPressed; - - var upButton = Overlays.addOverlay("text", { - x: position.x + arrowButtonSize, - y: position.y, - width: arrowButtonSize, - height: arrowButtonSize, - color: color, - backgroundColor: arrowButtonBackground, - topMargin: 4, - leftMargin: 4, - text: "^", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - var leftButton = Overlays.addOverlay("text", { - x: position.x, - y: position.y + arrowButtonSize, - width: arrowButtonSize, - height: arrowButtonSize, - color: color, - backgroundColor: arrowButtonBackground, - topMargin: 4, - leftMargin: 4, - text: "<", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - var rightButton = Overlays.addOverlay("text", { - x: position.x + (arrowButtonSize * 2), - y: position.y + arrowButtonSize, - width: arrowButtonSize, - height: arrowButtonSize, - color: color, - backgroundColor: arrowButtonBackground, - topMargin: 4, - leftMargin: 4, - text: ">", - alpha: 1.0, - visible: true, - }); - var downButton = Overlays.addOverlay("text", { - x: position.x + arrowButtonSize, - y: position.y + (arrowButtonSize * 2), - width: arrowButtonSize, - height: arrowButtonSize, - color: color, - backgroundColor: arrowButtonBackground, - topMargin: 4, - leftMargin: 4, - text: "v", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - var centerButton = Overlays.addOverlay("text", { - x: position.x + arrowButtonSize, - y: position.y + arrowButtonSize, - width: arrowButtonSize, - height: arrowButtonSize, - color: color, - backgroundColor: centerButtonBackground, - topMargin: 4, - leftMargin: 4, - text: "", - alpha: 1.0, - backgroundAlpha: 1.0, - visible: true, - }); - - var allOverlays = [ - upButton, - downButton, - leftButton, - rightButton, - centerButton, - ]; - - that.destroy = function() { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.deleteOverlay(allOverlays[i]); - } - }; - - that.setVisible = function(visible) { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.editOverlay(allOverlays[i], { visible: visible }); - } - } - - that.mouseReleaseEvent = function(event) { - var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - var clicked = false; - if (clickedOverlay == leftButton) { - if (onLeftPressed) onLeftPressed(); - clicked = true; - } else if (clickedOverlay == rightButton) { - if (onRightPressed) onRightPressed(); - clicked = true; - } else if (clickedOverlay == upButton) { - if (onUpPressed) onUpPressed(); - clicked = true; - } else if (clickedOverlay == downButton) { - if (onDownPressed) onDownPressed(); - clicked = true; - } else if (clickedOverlay == centerButton) { - if (onCenterPressed) onCenterPressed(); - clicked = true; - } - return clicked; - } - - return that; -} - - CameraTool = function(cameraManager) { var that = {}; - var toolsPosition = { x: 20, y: 280 }; - var orbitToolPosition = toolsPosition; - var panToolPosition = { x: toolsPosition.x + 80, y: toolsPosition.y }; - var zoomToolPosition = { x: toolsPosition.x + 20, y: toolsPosition.y + 80 }; + var RED = { red: 191, green: 78, blue: 38 }; + var GREEN = { red: 26, green: 193, blue: 105 }; + var BLUE = { red: 0, green: 131, blue: 204 }; - var orbitIncrement = 15; - orbitTool = ArrowTool({ - position: orbitToolPosition, - arrowBackground: { red: 192, green: 192, blue: 192 }, - centerBackground: { red: 128, green: 128, blue: 255 }, - color: { red: 0, green: 0, blue: 0 }, - onUpPressed: function() { cameraManager.addPitch(orbitIncrement); }, - onDownPressed: function() { cameraManager.addPitch(-orbitIncrement); }, - onLeftPressed: function() { cameraManager.addYaw(-orbitIncrement); }, - onRightPressed: function() { cameraManager.addYaw(orbitIncrement); }, - onCenterPressed: function() { cameraManager.focus(); }, + var ORIENTATION_OVERLAY_SIZE = 20; + var ORIENTATION_OVERLAY_HALF_SIZE = ORIENTATION_OVERLAY_SIZE / 2; + var ORIENTATION_OVERLAY_CUBE_SIZE = 8, + + var ORIENTATION_OVERLAY_OFFSET = { + x: 96, + y: 30, + } + + var UI_URL = HIFI_PUBLIC_BUCKET + "images/tools/camera-controls.svg"; + + var UI_WIDTH = 128; + var UI_HEIGHT = 61; + var UI_PADDING = 10; + + var UI_BUTTON_WIDTH = 64; + var UI_BUTTON_HEIGHT = 30; + + var UI_SUBIMAGE_FIRST_PERSON = { + x: 0, + y: 0, + width: UI_WIDTH, + height: UI_HEIGHT + }, + var UI_SUBIMAGE_THIRD_PERSON = { + x: 0, + y: UI_HEIGHT, + width: UI_WIDTH, + height: UI_HEIGHT + }, + var UI_SUBIMAGE_OTHER = { + x: 0, + y: UI_HEIGHT * 2, + width: UI_WIDTH, + height: UI_HEIGHT + }, + + var lastKnownWidth = Window.innerWidth; + + var uiPosition = { + x: lastKnownWidth - UI_WIDTH - UI_PADDING, + y: UI_PADDING, + }; + + var ui = Overlays.addOverlay("image", { + imageURL: UI_URL, + x: uiPosition.x, + y: uiPosition.y, + subImage: { + x: 0, + y: 0, + width: UI_WIDTH, + height: UI_HEIGHT + }, + width: UI_WIDTH, + height: UI_HEIGHT, + alpha: 1.0, + visible: true }); - panTool = ArrowTool({ - position: panToolPosition, - arrowBackground: { red: 192, green: 192, blue: 192 }, - centerBackground: { red: 128, green: 128, blue: 255 }, - color: { red: 0, green: 0, blue: 0 }, - onUpPressed: function() { cameraManager.pan({ x: 0, y: 15 }); }, - onDownPressed: function() { cameraManager.pan({ x: 0, y: -15 }); }, - onLeftPressed: function() { cameraManager.pan({ x: -15, y: 0 }); }, - onRightPressed: function() { cameraManager.pan({ x: 15, y: 0 }); }, - }); - zoomTool = ZoomTool({ - position: zoomToolPosition, - arrowBackground: { red: 192, green: 192, blue: 192 }, - color: { red: 0, green: 0, blue: 0 }, - onIncreasePressed: function() { cameraManager.addZoom(-10); }, - onDecreasePressed: function() { cameraManager.addZoom(10); }, - onPercentageSet: function(pct) { cameraManager.setZoomPercentage(pct); } + + var defaultCubeProps = { + size: ORIENTATION_OVERLAY_CUBE_SIZE, + alpha: 1, + color: { red: 255, green: 0, blue: 0 }, + solid: true, + visible: true, + drawOnHUD: true, + }; + var defaultLineProps = { + lineWidth: 1.5, + alpha: 1, + position: { x: 0, y: 0, z: 0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 255, green: 0, blue: 0 }, + visible: true, + drawOnHUD: true, + }; + + var orientationOverlay = OverlayGroup({ + position: { + x: uiPosition.x + ORIENTATION_OVERLAY_OFFSET.x, + y: uiPosition.y + ORIENTATION_OVERLAY_OFFSET.y, + } }); + var OOHS = ORIENTATION_OVERLAY_HALF_SIZE; + var cubeX = orientationOverlay.createOverlay("cube", mergeObjects(defaultCubeProps, { + position: { x: -OOHS, y: OOHS, z: OOHS }, + color: RED, + })); + var cubeY = orientationOverlay.createOverlay("cube", mergeObjects(defaultCubeProps, { + position: { x: OOHS, y: -OOHS, z: OOHS }, + color: GREEN, + })); + var cubeZ = orientationOverlay.createOverlay("cube", mergeObjects(defaultCubeProps, { + position: { x: OOHS, y: OOHS, z: -OOHS }, + color: BLUE, + })); + orientationOverlay.createOverlay("line3d", mergeObjects(defaultLineProps, { + start: { x: -OOHS, y: OOHS, z: OOHS }, + end: { x: OOHS, y: OOHS, z: OOHS }, + color: RED, + })); + orientationOverlay.createOverlay("line3d", mergeObjects(defaultLineProps, { + start: { x: OOHS, y: -OOHS, z: OOHS }, + end: { x: OOHS, y: OOHS, z: OOHS }, + color: GREEN, + })); + orientationOverlay.createOverlay("line3d", mergeObjects(defaultLineProps, { + start: { x: OOHS, y: OOHS, z: -OOHS }, + end: { x: OOHS, y: OOHS, z: OOHS }, + color: BLUE, + })); + Script.scriptEnding.connect(function() { - orbitTool.destroy(); - panTool.destroy(); - zoomTool.destroy(); + orientationOverlay.destroy(); + Overlays.deleteOverlay(ui); }); + var flip = Quat.fromPitchYawRollDegrees(0, 180, 0); + that.update = function() { + orientationOverlay.setProperties({ + rotation: Quat.multiply(flip, Quat.inverse(Camera.orientation)), + }); + + if (Window.innerWidth != lastKnownWidth) { + lastKnownWidth = Window.innerWidth; + uiPosition = { + x: lastKnownWidth - UI_WIDTH - UI_PADDING, + y: UI_PADDING, + }; + Overlays.editOverlay(ui, { + x: uiPosition.x, + y: uiPosition.y + }); + orientationOverlay.setProperties({ + position: { + x: uiPosition.x + ORIENTATION_OVERLAY_OFFSET.x, + y: uiPosition.y + ORIENTATION_OVERLAY_OFFSET.y, + } + }); + } + } + that.mousePressEvent = function(event) { - return orbitTool.mouseReleaseEvent(event) - || panTool.mouseReleaseEvent(event) - || zoomTool.mouseReleaseEvent(event); + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + if (clickedOverlay == cubeX) { + targetPitch = 0; + targetYaw = event.isLeftButton ? 90 : -90; + cameraManager.setTargetPitchYaw(targetPitch, targetYaw); + return true; + } else if (clickedOverlay == cubeY) { + targetPitch = event.isLeftButton ? 90 : -90; + targetYaw = 0; + cameraManager.setTargetPitchYaw(targetPitch, targetYaw); + return true; + } else if (clickedOverlay == cubeZ) { + targetPitch = 0; + targetYaw = event.isLeftButton ? 0 : 180; + cameraManager.setTargetPitchYaw(targetPitch, targetYaw); + return true; + } else if (clickedOverlay == ui) { + var x = event.x - uiPosition.x; + var y = event.y - uiPosition.y; + + // Did we hit a button? + if (x < UI_BUTTON_WIDTH) { + if (y < UI_BUTTON_HEIGHT) { + Camera.mode = "first person"; + } else { + Camera.mode = "third person"; + } + } + return true; + } }; + function updateMode() { + var mode = Camera.mode; + + var subImage = UI_SUBIMAGE_OTHER; + if (mode == "first person") { + subImage = UI_SUBIMAGE_FIRST_PERSON; + } else if (mode == "third person") { + subImage = UI_SUBIMAGE_THIRD_PERSON; + } + + Overlays.editOverlay(ui, { subImage: subImage }); + } + + Camera.modeUpdated.connect(updateMode); + updateMode(); + that.setVisible = function(visible) { - orbitTool.setVisible(visible); - panTool.setVisible(visible); - zoomTool.setVisible(visible); }; - Script.update.connect(function() { - cameraManager.getZoomPercentage(); - zoomTool.setZoomPercentage(cameraManager.getZoomPercentage()); - }); - - that.setVisible(false); - return that; }; diff --git a/examples/libraries/overlayUtils.js b/examples/libraries/overlayUtils.js new file mode 100644 index 0000000000..7623bfbb30 --- /dev/null +++ b/examples/libraries/overlayUtils.js @@ -0,0 +1,64 @@ +/** + * OverlayGroup provides a way to create composite overlays and control their + * position relative to a settable rootPosition and rootRotation. + */ +OverlayGroup = function(opts) { + var that = {}; + + var overlays = {}; + + var rootPosition = opts.position || { x: 0, y: 0, z: 0 }; + var rootRotation = opts.rotation || Quat.fromPitchYawRollRadians(0, 0, 0); + var visible = true; + + function updateOverlays() { + for (overlayID in overlays) { + var overlay = overlays[overlayID]; + var newPosition = Vec3.multiplyQbyV(rootRotation, overlay.position); + newPosition = Vec3.sum(rootPosition, newPosition); + Overlays.editOverlay(overlayID, { + visible: visible, + position: newPosition, + rotation: Quat.multiply(rootRotation, overlay.rotation), + }); + }; + } + + that.createOverlay = function(type, properties) { + properties.position = properties.position || { x: 0, y: 0, z: 0 }; + properties.rotation = properties.rotation || Quat.fromPitchYawRollRadians(0, 0, 0); + + var overlay = Overlays.addOverlay(type, properties); + + overlays[overlay] = { + position: properties.position, + rotation: properties.rotation, + }; + + updateOverlays(); + + return overlay; + } + + that.setProperties = function(properties) { + if (properties.position !== undefined) { + rootPosition = properties.position; + } + if (properties.rotation !== undefined) { + rootRotation = properties.rotation; + } + if (properties.visible !== undefined) { + visible = properties.visible; + } + updateOverlays(); + }; + + that.destroy = function() { + for (var overlay in overlays) { + Overlays.deleteOverlay(overlay); + } + overlays = {}; + } + + return that; +}; diff --git a/examples/overlaysExample.js b/examples/overlaysExample.js index 4aa5b1cd75..821f8a11d1 100644 --- a/examples/overlaysExample.js +++ b/examples/overlaysExample.js @@ -154,7 +154,7 @@ var sphere = Overlays.addOverlay("sphere", { }); var line3d = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z:0 }, + start: { x: 0, y: 0, z:0 }, end: { x: 10, y: 10, z:10 }, color: { red: 0, green: 255, blue: 255}, alpha: 1, diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 87f8eec72a..1765fc0fb5 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -38,6 +38,7 @@ endif () # set up the external glm library include_glm() +include_bullet() # create the InterfaceConfig.h file based on GL_HEADERS above configure_file(InterfaceConfig.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceConfig.h") diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c8018e41a0..0257707edf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #include #include #include @@ -150,6 +151,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _frameCount(0), _fps(60.0f), _justStarted(true), +#ifdef USE_BULLET_PHYSICS + _physicsEngine(glm::vec3(0.0f)), +#endif // USE_BULLET_PHYSICS _entities(true, this, this), _entityCollisionSystem(), _entityClipboardRenderer(false, this, this), @@ -1763,6 +1767,12 @@ void Application::init() { // save settings when avatar changes connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings); +#ifdef USE_BULLET_PHYSICS + EntityTree* tree = _entities.getTree(); + _physicsEngine.setEntityTree(tree); + tree->setSimulation(&_physicsEngine); + _physicsEngine.init(&_entityEditSender); +#endif // USE_BULLET_PHYSICS // make sure our texture cache knows about window size changes DependencyManager::get()->associateWithWidget(glCanvas.data()); @@ -2070,6 +2080,13 @@ void Application::update(float deltaTime) { updateDialogs(deltaTime); // update various stats dialogs if present updateCursor(deltaTime); // Handle cursor updates +#ifdef USE_BULLET_PHYSICS + { + PerformanceTimer perfTimer("physics"); + _physicsEngine.stepSimulation(); + } +#endif // USE_BULLET_PHYSICS + if (!_aboutToQuit) { PerformanceTimer perfTimer("entities"); // NOTE: the _entities.update() call below will wait for lock @@ -2615,12 +2632,12 @@ void Application::updateShadowMap() { // render JS/scriptable overlays { PerformanceTimer perfTimer("3dOverlays"); - _overlays.render3D(false, RenderArgs::SHADOW_RENDER_MODE); + _overlays.renderWorld(false, RenderArgs::SHADOW_RENDER_MODE); } { PerformanceTimer perfTimer("3dOverlaysFront"); - _overlays.render3D(true, RenderArgs::SHADOW_RENDER_MODE); + _overlays.renderWorld(true, RenderArgs::SHADOW_RENDER_MODE); } glDisable(GL_POLYGON_OFFSET_FILL); @@ -2840,7 +2857,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly, RenderAr // render JS/scriptable overlays { PerformanceTimer perfTimer("3dOverlays"); - _overlays.render3D(false); + _overlays.renderWorld(false); } // render the ambient occlusion effect if enabled @@ -2929,7 +2946,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly, RenderAr { PerformanceTimer perfTimer("3dOverlaysFront"); glClear(GL_DEPTH_BUFFER_BIT); - _overlays.render3D(true); + _overlays.renderWorld(true); } } @@ -3333,16 +3350,6 @@ void Application::updateWindowTitle(){ QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + AddressManager::getInstance().getCurrentDomain() + connectionStatus + buildVersion; - AccountManager& accountManager = AccountManager::getInstance(); - if (accountManager.getAccountInfo().hasBalance()) { - float creditBalance = accountManager.getAccountInfo().getBalance() / SATOSHIS_PER_CREDIT; - - QString creditBalanceString; - creditBalanceString.sprintf("%.8f", creditBalance); - - title += " - ₵" + creditBalanceString; - } - #ifndef WIN32 // crashes with vs2013/win32 qDebug("Application title set to: %s", title.toStdString().c_str()); @@ -3854,6 +3861,25 @@ void Application::openUrl(const QUrl& url) { } } +void Application::updateMyAvatarTransform() { + bumpSettings(); +#ifdef USE_BULLET_PHYSICS + const float SIMULATION_OFFSET_QUANTIZATION = 16.0f; // meters + glm::vec3 avatarPosition = _myAvatar->getPosition(); + glm::vec3 physicsWorldOffset = _physicsEngine.getOriginOffset(); + if (glm::distance(avatarPosition, physicsWorldOffset) > SIMULATION_OFFSET_QUANTIZATION) { + glm::vec3 newOriginOffset = avatarPosition; + int halfExtent = (int)HALF_SIMULATION_EXTENT; + for (int i = 0; i < 3; ++i) { + newOriginOffset[i] = (float)(glm::max(halfExtent, + ((int)(avatarPosition[i] / SIMULATION_OFFSET_QUANTIZATION)) * (int)SIMULATION_OFFSET_QUANTIZATION)); + } + // TODO: Andrew to replace this with method that actually moves existing object positions in PhysicsEngine + _physicsEngine.setOriginOffset(newOriginOffset); + } +#endif // USE_BULLET_PHYSICS +} + void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) { // from the domain-handler, figure out the satoshi cost per voxel and per meter cubed diff --git a/interface/src/Application.h b/interface/src/Application.h index 943d48bbac..58fb3fc342 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -46,6 +46,7 @@ #include "Menu.h" #include "MetavoxelSystem.h" #include "PacketHeaders.h" +#include "Physics.h" #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/AvatarManager.h" @@ -344,6 +345,7 @@ public slots: void openUrl(const QUrl& url); + void updateMyAvatarTransform(); void bumpSettings() { ++_numChangedSettings; } void domainSettingsReceived(const QJsonObject& domainSettingsObject); @@ -453,6 +455,10 @@ private: bool _justStarted; Stars _stars; +#ifdef USE_BULLET_PHYSICS + PhysicsEngine _physicsEngine; +#endif // USE_BULLET_PHYSICS + EntityTreeRenderer _entities; EntityCollisionSystem _entityCollisionSystem; EntityTreeRenderer _entityClipboardRenderer; diff --git a/interface/src/Physics.cpp b/interface/src/Physics.cpp index 23a6a56899..7efa520571 100644 --- a/interface/src/Physics.cpp +++ b/interface/src/Physics.cpp @@ -41,4 +41,4 @@ void applyDamping(float deltaTime, glm::vec3& velocity, float linearStrength, fl void applyDampedSpring(float deltaTime, glm::vec3& velocity, glm::vec3& position, glm::vec3& targetPosition, float k, float damping) { -} \ No newline at end of file +} diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 5a30e8634d..2e6c166bd3 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -46,13 +46,18 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { } void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { + Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); // get the rotation axes in joint space and use them to adjust the rotation glm::mat3 axes = glm::mat3_cast(glm::quat()); glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation))); - state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) - * glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1])) - * glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0])) + state.setRotationInConstrainedFrame( + glm::angleAxis(- RADIANS_PER_DEGREE * (_owningHead->getFinalRoll() - owningAvatar->getHead()->getFinalLeanSideways()), + glm::normalize(inverse * axes[2])) + * glm::angleAxis(RADIANS_PER_DEGREE * (_owningHead->getFinalYaw() - _owningHead->getTorsoTwist()), + glm::normalize(inverse * axes[1])) + * glm::angleAxis(- RADIANS_PER_DEGREE * (_owningHead->getFinalPitch() - owningAvatar->getHead()->getFinalLeanForward()), + glm::normalize(inverse * axes[0])) * joint.rotation, DEFAULT_PRIORITY); } diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 9c02b0e380..a7315a348b 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -54,6 +54,7 @@ Head::Head(Avatar* owningAvatar) : _deltaRoll(0.0f), _deltaLeanSideways(0.0f), _deltaLeanForward(0.0f), + _torsoTwist(0.0f), _isCameraMoving(false), _isLookingAtMe(false), _faceModel(this) @@ -88,7 +89,14 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); } } + // Twist the upper body to follow the rotation of the head, but only do this with my avatar, + // since everyone else will see the full joint rotations for other people. + const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; + const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; + float currentTwist = getTorsoTwist(); + setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); } + // Update audio trailing average for rendering facial animations const float AUDIO_AVERAGING_SECS = 0.05f; const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.0f; diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 6ae0d7c728..fdc7d927a3 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -76,6 +76,9 @@ public: float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; } float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; } + float getTorsoTwist() const { return _torsoTwist; } + void setTorsoTwist(float torsoTwist) { _torsoTwist = torsoTwist; } + glm::quat getEyeRotation(const glm::vec3& eyePosition) const; const glm::vec3& getRightEyePosition() const { return _rightEyePosition; } @@ -148,6 +151,8 @@ private: // delta lean angles for lean perturbations (driven by collisions) float _deltaLeanSideways; float _deltaLeanForward; + + float _torsoTwist; bool _isCameraMoving; bool _isLookingAtMe; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 77cbfd4e87..d0021dadad 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -297,11 +297,13 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, Joint } // get the rotation axes in joint space and use them to adjust the rotation glm::vec3 xAxis(1.0f, 0.0f, 0.0f); + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); glm::vec3 zAxis(0.0f, 0.0f, 1.0f); glm::quat inverse = glm::inverse(parentState.getRotation() * state.getDefaultRotationInParentFrame()); state.setRotationInConstrainedFrame( glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), inverse * zAxis) - * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis) + * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis) + * glm::angleAxis(RADIANS_PER_DEGREE * _owningAvatar->getHead()->getTorsoTwist(), inverse * yAxis) * state.getFBXJoint().rotation, LEAN_PRIORITY); } diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 24fbc35457..08a7465d45 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -182,8 +182,10 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { } glPushMatrix(); { + const float NEAR_CLIP = -10000; + const float FAR_CLIP = 10000; glLoadIdentity(); - glOrtho(0, glCanvas->width(), glCanvas->height(), 0, -1.0, 1.0); + glOrtho(0, glCanvas->width(), glCanvas->height(), 0, NEAR_CLIP, FAR_CLIP); renderAudioMeter(); @@ -196,7 +198,7 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { // give external parties a change to hook in emit application->renderingOverlay(); - overlays.render2D(); + overlays.renderHUD(); renderPointers(); @@ -1103,7 +1105,7 @@ void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { delete _framebufferObject; } - _framebufferObject = new QOpenGLFramebufferObject(size); + _framebufferObject = new QOpenGLFramebufferObject(size, QOpenGLFramebufferObject::Depth); bindTexture(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index a9588cd7a3..12e593a1d0 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -14,6 +14,7 @@ #include #include +#include "Application.h" #include "Base3DOverlay.h" const glm::vec3 DEFAULT_POSITION = glm::vec3(0.0f, 0.0f, 0.0f); @@ -28,7 +29,8 @@ Base3DOverlay::Base3DOverlay() : _isSolid(DEFAULT_IS_SOLID), _isDashedLine(DEFAULT_IS_DASHED_LINE), _ignoreRayIntersection(false), - _drawInFront(false) + _drawInFront(false), + _drawOnHUD(false) { } @@ -46,6 +48,13 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : Base3DOverlay::~Base3DOverlay() { } +void Base3DOverlay::setDrawOnHUD(bool value) { + if (_drawOnHUD != value) { + _drawOnHUD = value; + Application::getInstance()->getOverlays().overlayDrawOnChanged(this); + } +} + void Base3DOverlay::setProperties(const QScriptValue& properties) { Overlay::setProperties(properties); @@ -56,16 +65,20 @@ void Base3DOverlay::setProperties(const QScriptValue& properties) { setDrawInFront(value); } + QScriptValue drawOnHUD = properties.property("drawOnHUD"); + + if (drawOnHUD.isValid()) { + bool value = drawOnHUD.toVariant().toBool(); + setDrawOnHUD(value); + } + QScriptValue position = properties.property("position"); - // if "position" property was not there, check to see if they included aliases: start, point, p1 + // if "position" property was not there, check to see if they included aliases: point, p1 if (!position.isValid()) { - position = properties.property("start"); + position = properties.property("p1"); if (!position.isValid()) { - position = properties.property("p1"); - if (!position.isValid()) { - position = properties.property("point"); - } + position = properties.property("point"); } } @@ -162,6 +175,9 @@ QScriptValue Base3DOverlay::getProperty(const QString& property) { if (property == "drawInFront") { return _drawInFront; } + if (property == "drawOnHUD") { + return _drawOnHUD; + } return Overlay::getProperty(property); } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index b5314bd6d3..0e10a5f63b 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -37,6 +37,7 @@ public: const glm::quat& getRotation() const { return _rotation; } bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } bool getDrawInFront() const { return _drawInFront; } + bool getDrawOnHUD() const { return _drawOnHUD; } // setters void setPosition(const glm::vec3& position) { _position = position; } @@ -46,6 +47,7 @@ public: void setRotation(const glm::quat& value) { _rotation = value; } void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } void setDrawInFront(bool value) { _drawInFront = value; } + void setDrawOnHUD(bool value); virtual void setProperties(const QScriptValue& properties); virtual QScriptValue getProperty(const QString& property); @@ -67,6 +69,7 @@ protected: bool _isDashedLine; bool _ignoreRayIntersection; bool _drawInFront; + bool _drawOnHUD; }; #endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index c61d68f05d..2242a642ca 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -76,7 +76,13 @@ void Cube3DOverlay::render(RenderArgs* args) { glPushMatrix(); glColor4f(1.0f, 1.0f, 1.0f, alpha); glScalef(dimensions.x * _borderSize, dimensions.y * _borderSize, dimensions.z * _borderSize); - DependencyManager::get()->renderSolidCube(1.0f); + + if (_drawOnHUD) { + DependencyManager::get()->renderSolidCube(1.0f); + } else { + DependencyManager::get()->renderSolidCube(1.0f); + } + glPopMatrix(); glDepthMask(GL_TRUE); } @@ -84,7 +90,11 @@ void Cube3DOverlay::render(RenderArgs* args) { glPushMatrix(); glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); glScalef(dimensions.x, dimensions.y, dimensions.z); - DependencyManager::get()->renderSolidCube(1.0f); + if (_drawOnHUD) { + DependencyManager::get()->renderSolidCube(1.0f); + } else { + DependencyManager::get()->renderSolidCube(1.0f); + } glPopMatrix(); } else { glLineWidth(_lineWidth); diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 4facd779de..83cb48899c 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -39,6 +39,8 @@ void Line3DOverlay::render(RenderArgs* args) { glower = new Glower(glowLevel); } + glPushMatrix(); + glDisable(GL_LIGHTING); glLineWidth(_lineWidth); @@ -47,16 +49,25 @@ void Line3DOverlay::render(RenderArgs* args) { const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + glm::vec3 position = getPosition(); + glm::quat rotation = getRotation(); + + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + if (getIsDashedLine()) { drawDashedLine(_position, _end); } else { glBegin(GL_LINES); - glVertex3f(_position.x, _position.y, _position.z); + glVertex3f(_start.x, _start.y, _start.z); glVertex3f(_end.x, _end.y, _end.z); glEnd(); } glEnable(GL_LIGHTING); + glPopMatrix(); + if (glower) { delete glower; } @@ -65,13 +76,28 @@ void Line3DOverlay::render(RenderArgs* args) { void Line3DOverlay::setProperties(const QScriptValue& properties) { Base3DOverlay::setProperties(properties); + QScriptValue start = properties.property("start"); + // if "start" property was not there, check to see if they included aliases: startPoint + if (!start.isValid()) { + start = properties.property("startPoint"); + } + if (start.isValid()) { + QScriptValue x = start.property("x"); + QScriptValue y = start.property("y"); + QScriptValue z = start.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + glm::vec3 newStart; + newStart.x = x.toVariant().toFloat(); + newStart.y = y.toVariant().toFloat(); + newStart.z = z.toVariant().toFloat(); + setStart(newStart); + } + } + QScriptValue end = properties.property("end"); - // if "end" property was not there, check to see if they included aliases: endPoint, or p2 + // if "end" property was not there, check to see if they included aliases: endPoint if (!end.isValid()) { end = properties.property("endPoint"); - if (!end.isValid()) { - end = properties.property("p2"); - } } if (end.isValid()) { QScriptValue x = end.property("x"); diff --git a/interface/src/ui/overlays/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h index 607fb0e0bb..afe0ade585 100644 --- a/interface/src/ui/overlays/Line3DOverlay.h +++ b/interface/src/ui/overlays/Line3DOverlay.h @@ -23,9 +23,11 @@ public: virtual void render(RenderArgs* args); // getters + const glm::vec3& getStart() const { return _start; } const glm::vec3& getEnd() const { return _end; } // setters + void setStart(const glm::vec3& start) { _start = start; } void setEnd(const glm::vec3& end) { _end = end; } virtual void setProperties(const QScriptValue& properties); @@ -34,6 +36,7 @@ public: virtual Line3DOverlay* createClone() const; protected: + glm::vec3 _start; glm::vec3 _end; }; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 19f2bda526..a9bd004576 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -36,14 +36,14 @@ Overlays::~Overlays() { { QWriteLocker lock(&_lock); - foreach(Overlay* thisOverlay, _overlays2D) { + foreach(Overlay* thisOverlay, _overlaysHUD) { delete thisOverlay; } - _overlays2D.clear(); - foreach(Overlay* thisOverlay, _overlays3D) { + _overlaysHUD.clear(); + foreach(Overlay* thisOverlay, _overlaysWorld) { delete thisOverlay; } - _overlays3D.clear(); + _overlaysWorld.clear(); } if (!_overlaysToDelete.isEmpty()) { @@ -64,10 +64,10 @@ void Overlays::update(float deltatime) { { QWriteLocker lock(&_lock); - foreach(Overlay* thisOverlay, _overlays2D) { + foreach(Overlay* thisOverlay, _overlaysHUD) { thisOverlay->update(deltatime); } - foreach(Overlay* thisOverlay, _overlays3D) { + foreach(Overlay* thisOverlay, _overlaysWorld) { thisOverlay->update(deltatime); } } @@ -81,21 +81,31 @@ void Overlays::update(float deltatime) { } -void Overlays::render2D() { +void Overlays::renderHUD() { QReadLocker lock(&_lock); RenderArgs args = { NULL, Application::getInstance()->getViewFrustum(), Menu::getInstance()->getOctreeSizeScale(), Menu::getInstance()->getBoundaryLevelAdjust(), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - foreach(Overlay* thisOverlay, _overlays2D) { - thisOverlay->render(&args); + foreach(Overlay* thisOverlay, _overlaysHUD) { + if (thisOverlay->is3D()) { + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); + + thisOverlay->render(&args); + + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + } else{ + thisOverlay->render(&args); + } } } -void Overlays::render3D(bool drawFront, RenderArgs::RenderMode renderMode, RenderArgs::RenderSide renderSide) { +void Overlays::renderWorld(bool drawFront, RenderArgs::RenderMode renderMode, RenderArgs::RenderSide renderSide) { QReadLocker lock(&_lock); - if (_overlays3D.size() == 0) { + if (_overlaysWorld.size() == 0) { return; } bool myAvatarComputed = false; @@ -111,7 +121,7 @@ void Overlays::render3D(bool drawFront, RenderArgs::RenderMode renderMode, Rende renderMode, renderSide, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - foreach(Overlay* thisOverlay, _overlays3D) { + foreach(Overlay* thisOverlay, _overlaysWorld) { Base3DOverlay* overlay3D = static_cast(thisOverlay); if (overlay3D->getDrawInFront() != drawFront) { continue; @@ -190,9 +200,14 @@ unsigned int Overlays::addOverlay(Overlay* overlay) { unsigned int thisID = _nextOverlayID; _nextOverlayID++; if (overlay->is3D()) { - _overlays3D[thisID] = overlay; + Base3DOverlay* overlay3D = static_cast(overlay); + if (overlay3D->getDrawOnHUD()) { + _overlaysHUD[thisID] = overlay; + } else { + _overlaysWorld[thisID] = overlay; + } } else { - _overlays2D[thisID] = overlay; + _overlaysHUD[thisID] = overlay; } return thisID; @@ -200,21 +215,23 @@ unsigned int Overlays::addOverlay(Overlay* overlay) { unsigned int Overlays::cloneOverlay(unsigned int id) { Overlay* thisOverlay = NULL; - if (_overlays2D.contains(id)) { - thisOverlay = _overlays2D[id]; - } else if (_overlays3D.contains(id)) { - thisOverlay = _overlays3D[id]; + if (_overlaysHUD.contains(id)) { + thisOverlay = _overlaysHUD[id]; + } else if (_overlaysWorld.contains(id)) { + thisOverlay = _overlaysWorld[id]; } return addOverlay(thisOverlay->createClone()); } bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) { Overlay* thisOverlay = NULL; - QWriteLocker lock(&_lock); - if (_overlays2D.contains(id)) { - thisOverlay = _overlays2D[id]; - } else if (_overlays3D.contains(id)) { - thisOverlay = _overlays3D[id]; + { + QReadLocker lock(&_lock); + if (_overlaysHUD.contains(id)) { + thisOverlay = _overlaysHUD[id]; + } else if (_overlaysWorld.contains(id)) { + thisOverlay = _overlaysWorld[id]; + } } if (thisOverlay) { thisOverlay->setProperties(properties); @@ -228,10 +245,10 @@ void Overlays::deleteOverlay(unsigned int id) { { QWriteLocker lock(&_lock); - if (_overlays2D.contains(id)) { - overlayToDelete = _overlays2D.take(id); - } else if (_overlays3D.contains(id)) { - overlayToDelete = _overlays3D.take(id); + if (_overlaysHUD.contains(id)) { + overlayToDelete = _overlaysHUD.take(id); + } else if (_overlaysWorld.contains(id)) { + overlayToDelete = _overlaysWorld.take(id); } else { return; } @@ -248,17 +265,34 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) { } QReadLocker lock(&_lock); - QMapIterator i(_overlays2D); + QMapIterator i(_overlaysHUD); i.toBack(); + + const float LARGE_NEGATIVE_FLOAT = -9999999; + glm::vec3 origin(pointCopy.x, pointCopy.y, LARGE_NEGATIVE_FLOAT); + glm::vec3 direction(0, 0, 1); + BoxFace thisFace; + float distance; + while (i.hasPrevious()) { i.previous(); unsigned int thisID = i.key(); - Overlay2D* thisOverlay = static_cast(i.value()); - if (thisOverlay->getVisible() && thisOverlay->isLoaded() && - thisOverlay->getBounds().contains(pointCopy.x, pointCopy.y, false)) { - return thisID; + if (i.value()->is3D()) { + Base3DOverlay* thisOverlay = static_cast(i.value()); + if (!thisOverlay->getIgnoreRayIntersection()) { + if (thisOverlay->findRayIntersection(origin, direction, distance, thisFace)) { + return thisID; + } + } + } else { + Overlay2D* thisOverlay = static_cast(i.value()); + if (thisOverlay->getVisible() && thisOverlay->isLoaded() && + thisOverlay->getBounds().contains(pointCopy.x, pointCopy.y, false)) { + return thisID; + } } } + return 0; // not found } @@ -266,10 +300,10 @@ OverlayPropertyResult Overlays::getProperty(unsigned int id, const QString& prop OverlayPropertyResult result; Overlay* thisOverlay = NULL; QReadLocker lock(&_lock); - if (_overlays2D.contains(id)) { - thisOverlay = _overlays2D[id]; - } else if (_overlays3D.contains(id)) { - thisOverlay = _overlays3D[id]; + if (_overlaysHUD.contains(id)) { + thisOverlay = _overlaysHUD[id]; + } else if (_overlaysWorld.contains(id)) { + thisOverlay = _overlaysWorld[id]; } if (thisOverlay) { result.value = thisOverlay->getProperty(property); @@ -311,7 +345,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray) float bestDistance = std::numeric_limits::max(); bool bestIsFront = false; RayToOverlayIntersectionResult result; - QMapIterator i(_overlays3D); + QMapIterator i(_overlaysWorld); i.toBack(); while (i.hasPrevious()) { i.previous(); @@ -416,13 +450,32 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R value.extraInfo = object.property("extraInfo").toVariant().toString(); } +void Overlays::overlayDrawOnChanged(Base3DOverlay* overlay) { + QWriteLocker lock(&_lock); + if (overlay->getDrawOnHUD()) { + for (unsigned int id : _overlaysWorld.keys()) { + if (_overlaysWorld[id] == overlay) { + _overlaysWorld.remove(id); + _overlaysHUD[id] = overlay; + } + } + } else { + for (unsigned int id : _overlaysHUD.keys()) { + if (_overlaysHUD[id] == overlay) { + _overlaysHUD.remove(id); + _overlaysWorld[id] = overlay; + } + } + } +} + bool Overlays::isLoaded(unsigned int id) { QReadLocker lock(&_lock); Overlay* thisOverlay = NULL; - if (_overlays2D.contains(id)) { - thisOverlay = _overlays2D[id]; - } else if (_overlays3D.contains(id)) { - thisOverlay = _overlays3D[id]; + if (_overlaysHUD.contains(id)) { + thisOverlay = _overlaysHUD[id]; + } else if (_overlaysWorld.contains(id)) { + thisOverlay = _overlaysWorld[id]; } else { return false; // not found } @@ -430,13 +483,13 @@ bool Overlays::isLoaded(unsigned int id) { } QSizeF Overlays::textSize(unsigned int id, const QString& text) const { - Overlay* thisOverlay = _overlays2D[id]; + Overlay* thisOverlay = _overlaysHUD[id]; if (thisOverlay) { if (typeid(*thisOverlay) == typeid(TextOverlay)) { return static_cast(thisOverlay)->textSize(text); } } else { - thisOverlay = _overlays3D[id]; + thisOverlay = _overlaysWorld[id]; if (thisOverlay) { if (typeid(*thisOverlay) == typeid(Text3DOverlay)) { return static_cast(thisOverlay)->textSize(text); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index fb2c936a64..d3030b0ac1 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -15,6 +15,7 @@ #include #include +#include "Base3DOverlay.h" #include "Overlay.h" class OverlayPropertyResult { @@ -52,9 +53,9 @@ public: ~Overlays(); void init(QGLWidget* parent); void update(float deltatime); - void render3D(bool drawFront, RenderArgs::RenderMode renderMode = RenderArgs::DEFAULT_RENDER_MODE, + void renderWorld(bool drawFront, RenderArgs::RenderMode renderMode = RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::RenderSide renderSide = RenderArgs::MONO); - void render2D(); + void renderHUD(); public slots: /// adds an overlay with the specific properties @@ -81,6 +82,9 @@ public slots: /// returns details about the closest 3D Overlay hit by the pick ray RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray); + + // called by Base3DOverlay when drawOnHUD changes + void overlayDrawOnChanged(Base3DOverlay* overlay); /// returns whether the overlay's assets are loaded or not bool isLoaded(unsigned int id); @@ -90,8 +94,8 @@ public slots: QSizeF textSize(unsigned int id, const QString& text) const; private: - QMap _overlays2D; - QMap _overlays3D; + QMap _overlaysHUD; + QMap _overlaysWorld; QList _overlaysToDelete; unsigned int _nextOverlayID; QGLWidget* _parent; diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index b7731a0e3d..774653497d 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -5,7 +5,7 @@ setup_hifi_library(Network Script) include_glm() -link_hifi_libraries(audio shared octree networking physics gpu model fbx) +link_hifi_libraries(audio shared octree networking gpu model fbx) # call macro to include our dependency includes and bubble them up via a property on our target include_dependency_includes() diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 2bdb203034..511ab50c11 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -73,16 +73,3 @@ void HeadData::setBlendshape(QString name, float val) { _blendshapeCoefficients[it.value()] = val; } } - -void HeadData::addYaw(float yaw) { - setBaseYaw(_baseYaw + yaw); -} - -void HeadData::addPitch(float pitch) { - setBasePitch(_basePitch + pitch); -} - -void HeadData::addRoll(float roll) { - setBaseRoll(_baseRoll + roll); -} - diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index ea7666a6cd..3a4eb3c808 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -65,11 +65,6 @@ public: float getPupilDilation() const { return _pupilDilation; } void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; } - // degrees - void addYaw(float yaw); - void addPitch(float pitch); - void addRoll(float roll); - const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 66078b387d..4f0f1c80a8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -39,6 +39,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf OctreeRenderer(), _wantScripts(wantScripts), _entitiesScriptEngine(NULL), + _sandboxScriptEngine(NULL), _lastMouseEventValid(false), _viewState(viewState), _scriptingServices(scriptingServices), @@ -58,7 +59,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf } EntityTreeRenderer::~EntityTreeRenderer() { - // do we need to delete the _entitiesScriptEngine?? or is it deleted by default + // NOTE: we don't need to delete _entitiesScriptEngine because it's owned by the application and gets cleaned up + // automatically but we do need to delete our sandbox script engine. + delete _sandboxScriptEngine; + _sandboxScriptEngine = NULL; } void EntityTreeRenderer::clear() { @@ -79,6 +83,8 @@ void EntityTreeRenderer::init() { _entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities", _scriptingServices->getControllerScriptingInterface()); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); + + _sandboxScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities Sandbox", NULL); } // make sure our "last avatar position" is something other than our current position, so that on our @@ -190,13 +196,15 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { if (isURL) { _entitiesScriptEngine->setParentURL(entity->getScript()); } - QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents); + QScriptValue entityScriptConstructor = _sandboxScriptEngine->evaluate(scriptContents); if (!entityScriptConstructor.isFunction()) { qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; qDebug() << " NOT CONSTRUCTOR"; qDebug() << " SCRIPT:" << entityScript; return QScriptValue(); // invalid script + } else { + entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents); } QScriptValue entityScriptObject = entityScriptConstructor.construct(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 2f88e854da..3826a80238 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -134,6 +134,7 @@ private: bool _wantScripts; ScriptEngine* _entitiesScriptEngine; + ScriptEngine* _sandboxScriptEngine; QScriptValue loadEntityScript(EntityItem* entity); QScriptValue loadEntityScript(const EntityItemID& entityItemID); diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index 21afff496f..2da7745837 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -5,7 +5,7 @@ setup_hifi_library(Network Script) include_glm() -link_hifi_libraries(avatars shared octree gpu model fbx networking animation physics) +link_hifi_libraries(avatars shared octree gpu model fbx networking animation) # call macro to include our dependency includes and bubble them up via a property on our target include_dependency_includes() diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp index cece544263..88e1a4b6b1 100644 --- a/libraries/entities/src/BoxEntityItem.cpp +++ b/libraries/entities/src/BoxEntityItem.cpp @@ -14,9 +14,9 @@ #include +#include "BoxEntityItem.h" #include "EntityTree.h" #include "EntityTreeElement.h" -#include "BoxEntityItem.h" EntityItem* BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { @@ -95,3 +95,9 @@ void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); } + +void BoxEntityItem::computeShapeInfo(ShapeInfo& info) const { + glm::vec3 halfExtents = 0.5f * getDimensionsInMeters(); + info.setBox(halfExtents); +} + diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h index da9e7544c6..3ce67369ee 100644 --- a/libraries/entities/src/BoxEntityItem.h +++ b/libraries/entities/src/BoxEntityItem.h @@ -46,11 +46,13 @@ public: void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } void setColor(const xColor& value) { - _color[RED_INDEX] = value.red; - _color[GREEN_INDEX] = value.green; - _color[BLUE_INDEX] = value.blue; + _color[RED_INDEX] = value.red; + _color[GREEN_INDEX] = value.green; + _color[BLUE_INDEX] = value.blue; } + void computeShapeInfo(ShapeInfo& info) const; + protected: rgbColor _color; }; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 1b9e0d6f86..5b05454784 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -90,6 +90,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) { _lastEditedFromRemote = 0; _lastEditedFromRemoteInRemoteTime = 0; _created = UNKNOWN_CREATED_TIME; + _physicsInfo = NULL; _dirtyFlags = 0; _changedOnServer = 0; initFromEntityItemID(entityItemID); @@ -104,12 +105,18 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert _lastEditedFromRemote = 0; _lastEditedFromRemoteInRemoteTime = 0; _created = UNKNOWN_CREATED_TIME; + _physicsInfo = NULL; _dirtyFlags = 0; _changedOnServer = 0; initFromEntityItemID(entityItemID); setProperties(properties); } +EntityItem::~EntityItem() { + // be sure to clean up _physicsInfo before calling this dtor + assert(_physicsInfo == NULL); +} + EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties; @@ -136,7 +143,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData) const { - // ALL this fits... // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] @@ -514,7 +520,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity); READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping); READ_ENTITY_PROPERTY_SETTER(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript); + READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT, setScript); READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint); READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping); @@ -581,6 +587,11 @@ bool EntityItem::isRestingOnSurface() const { } void EntityItem::simulate(const quint64& now) { + if (_physicsInfo) { + // we rely on bullet for simulation, so bail + return; + } + bool wantDebug = false; if (_lastSimulated == 0) { @@ -793,7 +804,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, updateScript); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, updateAngularVelocity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping); @@ -975,15 +986,29 @@ float EntityItem::getRadius() const { return radius; } +void EntityItem::computeShapeInfo(ShapeInfo& info) const { + info.clear(); +} + void EntityItem::recalculateCollisionShape() { AACube entityAACube = getMinimumAACube(); entityAACube.scale(TREE_SCALE); // scale to meters _collisionShape.setTranslation(entityAACube.calcCenter()); _collisionShape.setScale(entityAACube.getScale()); + // TODO: use motionState to update physics object } +const float MIN_POSITION_DELTA = 0.0001f; +const float MIN_DIMENSION_DELTA = 0.0001f; +const float MIN_ALIGNMENT_DOT = 0.9999f; +const float MIN_MASS_DELTA = 0.001f; +const float MIN_VELOCITY_DELTA = 0.025f; +const float MIN_GRAVITY_DELTA = 0.001f; +const float MIN_SPIN_DELTA = 0.0003f; + void EntityItem::updatePosition(const glm::vec3& value) { - if (_position != value) { + glm::vec3 debugPosition = value * (float) TREE_SCALE; + if (glm::distance(_position, value) * (float)TREE_SCALE > MIN_POSITION_DELTA) { _position = value; recalculateCollisionShape(); _dirtyFlags |= EntityItem::DIRTY_POSITION; @@ -992,7 +1017,7 @@ void EntityItem::updatePosition(const glm::vec3& value) { void EntityItem::updatePositionInMeters(const glm::vec3& value) { glm::vec3 position = glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f); - if (_position != position) { + if (glm::distance(_position, position) * (float)TREE_SCALE > MIN_POSITION_DELTA) { _position = position; recalculateCollisionShape(); _dirtyFlags |= EntityItem::DIRTY_POSITION; @@ -1000,24 +1025,24 @@ void EntityItem::updatePositionInMeters(const glm::vec3& value) { } void EntityItem::updateDimensions(const glm::vec3& value) { - if (_dimensions != value) { + if (glm::distance(_dimensions, value) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) { _dimensions = value; recalculateCollisionShape(); - _dirtyFlags |= EntityItem::DIRTY_SHAPE; + _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); } } void EntityItem::updateDimensionsInMeters(const glm::vec3& value) { glm::vec3 dimensions = value / (float) TREE_SCALE; - if (_dimensions != dimensions) { + if (glm::distance(_dimensions, dimensions) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) { _dimensions = dimensions; recalculateCollisionShape(); - _dirtyFlags |= EntityItem::DIRTY_SHAPE; + _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); } } void EntityItem::updateRotation(const glm::quat& rotation) { - if (_rotation != rotation) { + if (glm::dot(_rotation, rotation) < MIN_ALIGNMENT_DOT) { _rotation = rotation; recalculateCollisionShape(); _dirtyFlags |= EntityItem::DIRTY_POSITION; @@ -1025,29 +1050,37 @@ void EntityItem::updateRotation(const glm::quat& rotation) { } void EntityItem::updateMass(float value) { - if (_mass != value) { + if (fabsf(_mass - value) > MIN_MASS_DELTA) { _mass = value; _dirtyFlags |= EntityItem::DIRTY_MASS; } } -void EntityItem::updateVelocity(const glm::vec3& value) { - if (_velocity != value) { - _velocity = value; +void EntityItem::updateVelocity(const glm::vec3& value) { + if (glm::distance(_velocity, value) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { + if (glm::length(value) * (float)TREE_SCALE < MIN_VELOCITY_DELTA) { + _velocity = glm::vec3(0.0f); + } else { + _velocity = value; + } _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateVelocityInMeters(const glm::vec3& value) { glm::vec3 velocity = value / (float) TREE_SCALE; - if (_velocity != velocity) { - _velocity = velocity; + if (glm::distance(_velocity, velocity) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { + if (glm::length(value) < MIN_VELOCITY_DELTA) { + _velocity = glm::vec3(0.0f); + } else { + _velocity = velocity; + } _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateGravity(const glm::vec3& value) { - if (_gravity != value) { + if (glm::distance(_gravity, value) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) { _gravity = value; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } @@ -1055,14 +1088,14 @@ void EntityItem::updateGravity(const glm::vec3& value) { void EntityItem::updateGravityInMeters(const glm::vec3& value) { glm::vec3 gravity = value / (float) TREE_SCALE; - if (_gravity != gravity) { + if ( glm::distance(_gravity, gravity) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) { _gravity = gravity; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateAngularVelocity(const glm::vec3& value) { - if (_angularVelocity != value) { + if (glm::distance(_angularVelocity, value) > MIN_SPIN_DELTA) { _angularVelocity = value; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } @@ -1089,10 +1122,3 @@ void EntityItem::updateLifetime(float value) { } } -void EntityItem::updateScript(const QString& value) { - if (_script != value) { - _script = value; - _dirtyFlags |= EntityItem::DIRTY_SCRIPT; - } -} - diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4ac7e69bad..75a6b18244 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -22,6 +22,7 @@ #include // for EncodeBitstreamParams class #include // for OctreeElement::AppendState #include +#include #include "EntityItemID.h" #include "EntityItemProperties.h" @@ -49,16 +50,13 @@ public: DIRTY_SHAPE = 0x0020, DIRTY_LIFETIME = 0x0040, DIRTY_UPDATEABLE = 0x0080, - // add new simulation-relevant flags above - // all other flags below - DIRTY_SCRIPT = 0x8000 }; DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly EntityItem(const EntityItemID& entityItemID); EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); - virtual ~EntityItem() { } + virtual ~EntityItem(); // ID and EntityItemID related methods QUuid getID() const { return _id; } @@ -275,6 +273,7 @@ public: void applyHardCollision(const CollisionInfo& collisionInfo); virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; } virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } + virtual void computeShapeInfo(ShapeInfo& info) const; // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags void updatePosition(const glm::vec3& value); @@ -291,13 +290,15 @@ public: void updateIgnoreForCollisions(bool value); void updateCollisionsWillMove(bool value); void updateLifetime(float value); - void updateScript(const QString& value); uint32_t getDirtyFlags() const { return _dirtyFlags; } void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; } bool isMoving() const; + void* getPhysicsInfo() const { return _physicsInfo; } + void setPhysicsInfo(void* data) { _physicsInfo = data; } + protected: virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init @@ -309,9 +310,9 @@ protected: bool _newlyCreated; quint64 _lastSimulated; // last time this entity called simulate() quint64 _lastUpdated; // last time this entity called update() - quint64 _lastEdited; // this is the last official local or remote edit time - quint64 _lastEditedFromRemote; // this is the last time we received and edit from the server - quint64 _lastEditedFromRemoteInRemoteTime; // time in server time space the last time we received and edit from the server + quint64 _lastEdited; // last official local or remote edit time + quint64 _lastEditedFromRemote; // last time we received and edit from the server + quint64 _lastEditedFromRemoteInRemoteTime; // last time we received and edit from the server (in server-time-frame) quint64 _created; quint64 _changedOnServer; @@ -343,6 +344,10 @@ protected: AACubeShape _collisionShape; + // _physicsInfo is a hook reserved for use by the EntitySimulation, which is guaranteed to set _physicsInfo + // to a non-NULL value when the EntityItem has a representation in the physics engine. + void* _physicsInfo; // only set by EntitySimulation + // DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about. uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation }; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index b843c85327..f3424aee43 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -134,6 +134,13 @@ public: void debugDump() const; void setLastEdited(quint64 usecTime); + // Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type) creates the following methods and variables: + // type getFoo() const; + // void setFoo(type); + // bool fooChanged() const; + // type _foo; + // bool _fooChanged; + DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool); DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3); DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 6d3b81715d..09f75c9ebf 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -101,12 +101,13 @@ void EntitySimulation::sortEntitiesThatMoved() { } ++itemItr; } - _entitiesToBeSorted.clear(); - if (moveOperator.hasMovingEntities()) { PerformanceTimer perfTimer("recurseTreeWithOperator"); _entityTree->recurseTreeWithOperator(&moveOperator); } + + sortEntitiesThatMovedInternal(); + _entitiesToBeSorted.clear(); } void EntitySimulation::addEntity(EntityItem* entity) { @@ -122,6 +123,10 @@ void EntitySimulation::addEntity(EntityItem* entity) { _updateableEntities.insert(entity); } addEntityInternal(entity); + + // DirtyFlags are used to signal changes to entities that have already been added, + // so we can clear them for this entity which has just been added. + entity->clearDirtyFlags(); } void EntitySimulation::removeEntity(EntityItem* entity) { @@ -173,7 +178,6 @@ void EntitySimulation::entityChanged(EntityItem* entity) { } entityChangedInternal(entity); } - entity->clearDirtyFlags(); } void EntitySimulation::clearEntities() { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 506e2ed9d0..a32a1226c7 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -16,8 +16,21 @@ #include +#include "EntityItem.h" #include "EntityTree.h" +// the EntitySimulation needs to know when these things change on an entity, +// so it can sort EntityItem or relay its state to the PhysicsEngine. +const int DIRTY_SIMULATION_FLAGS = + EntityItem::DIRTY_POSITION | + EntityItem::DIRTY_VELOCITY | + EntityItem::DIRTY_MASS | + EntityItem::DIRTY_COLLISION_GROUP | + EntityItem::DIRTY_MOTION_TYPE | + EntityItem::DIRTY_SHAPE | + EntityItem::DIRTY_LIFETIME | + EntityItem::DIRTY_UPDATEABLE; + class EntitySimulation { public: EntitySimulation() : _entityTree(NULL) { } @@ -49,10 +62,18 @@ protected: // These pure virtual methods are protected because they are not to be called will-nilly. The base class // calls them in the right places. + + // NOTE: updateEntitiesInternal() should clear all dirty flags on each changed entity as side effect virtual void updateEntitiesInternal(const quint64& now) = 0; + virtual void addEntityInternal(EntityItem* entity) = 0; + virtual void removeEntityInternal(EntityItem* entity) = 0; + virtual void entityChangedInternal(EntityItem* entity) = 0; + + virtual void sortEntitiesThatMovedInternal() {} + virtual void clearEntitiesInternal() = 0; void expireMortalEntities(const quint64& now); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3580e64b35..73d944b084 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -132,19 +132,21 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro } } } else { - QString entityScriptBefore = entity->getScript(); - + uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); _isDirty = true; - if (_simulation && entity->getDirtyFlags() != 0) { - _simulation->entityChanged(entity); - } - - QString entityScriptAfter = entity->getScript(); - if (entityScriptBefore != entityScriptAfter) { - emit entityScriptChanging(entity->getEntityItemID()); // the entity script has changed + uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; + if (newFlags) { + if (_simulation) { + if (newFlags & DIRTY_SIMULATION_FLAGS) { + _simulation->entityChanged(entity); + } + } else { + // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly + entity->clearDirtyFlags(); + } } } diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 6fd2d7c48c..6d45768c26 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -14,7 +14,6 @@ #include "EntityItem.h" #include "SimpleEntitySimulation.h" - void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { QSet::iterator itemItr = _movingEntities.begin(); while (itemItr != _movingEntities.end()) { @@ -57,6 +56,7 @@ void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) { _movableButStoppedEntities.remove(entity); } } + entity->clearDirtyFlags(); } void SimpleEntitySimulation::clearEntitiesInternal() { diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 06035f6b6e..9a9c07ef9a 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -97,6 +97,12 @@ void SphereEntityItem::recalculateCollisionShape() { _sphereShape.setRadius(largestDiameter / 2.0f); } +void SphereEntityItem::computeShapeInfo(ShapeInfo& info) const { + glm::vec3 halfExtents = 0.5f * getDimensionsInMeters(); + // TODO: support ellipsoid shapes + info.setSphere(halfExtents.x); +} + bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const { @@ -122,6 +128,3 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons } return false; } - - - diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index 3b7dccaff9..c81f80d9ab 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -55,6 +55,8 @@ public: // TODO: implement proper contains for 3D ellipsoid //virtual bool contains(const glm::vec3& point) const; + + void computeShapeInfo(ShapeInfo& info) const; virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index cd90857637..f4ac2263c2 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -5,7 +5,7 @@ setup_hifi_library() include_glm() -link_hifi_libraries(shared networking physics) +link_hifi_libraries(shared networking) # find ZLIB find_package(ZLIB REQUIRED) diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index 4f74038ff5..416ffa49f1 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -4,16 +4,13 @@ set(TARGET_NAME physics) setup_hifi_library() include_glm() +include_bullet() +if (BULLET_FOUND) + target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) +endif (BULLET_FOUND) -link_hifi_libraries(shared) - -## find BULLET -#find_package(BULLET REQUIRED) -# -#include_directories(SYSTEM "${BULLET_INCLUDE_DIRS}") -# -## append BULLET to our list of libraries to link -#list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${BULLET_LIBRARIES}") +link_hifi_libraries(shared fbx entities) +include_hifi_library_headers(fbx) # call macro to include our dependency includes and bubble them up via a property on our target include_dependency_includes() diff --git a/libraries/physics/src/BulletUtil.h b/libraries/physics/src/BulletUtil.h new file mode 100644 index 0000000000..78524a122e --- /dev/null +++ b/libraries/physics/src/BulletUtil.h @@ -0,0 +1,38 @@ +// +// BulletUtil.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.02 +// 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_BulletUtil_h +#define hifi_BulletUtil_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include +#include + +inline glm::vec3 bulletToGLM(const btVector3& b) { + return glm::vec3(b.getX(), b.getY(), b.getZ()); +} + +inline glm::quat bulletToGLM(const btQuaternion& b) { + return glm::quat(b.getW(), b.getX(), b.getY(), b.getZ()); +} + +inline btVector3 glmToBullet(const glm::vec3& g) { + return btVector3(g.x, g.y, g.z); +} + +inline btQuaternion glmToBullet(const glm::quat& g) { + return btQuaternion(g.x, g.y, g.z, g.w); +} + +#endif // USE_BULLET_PHYSICS +#endif // hifi_BulletUtil_h diff --git a/libraries/physics/src/Constraint.h b/libraries/physics/src/Constraint.h index 9bbdc185e1..ed97d6cc73 100644 --- a/libraries/physics/src/Constraint.h +++ b/libraries/physics/src/Constraint.h @@ -1,6 +1,6 @@ // // Constraint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactConstraint.cpp b/libraries/physics/src/ContactConstraint.cpp index 9d1a92bb21..9734922a52 100644 --- a/libraries/physics/src/ContactConstraint.cpp +++ b/libraries/physics/src/ContactConstraint.cpp @@ -1,6 +1,6 @@ // // ContactConstraint.cpp -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactConstraint.h b/libraries/physics/src/ContactConstraint.h index 41be2f769d..44c9c1b879 100644 --- a/libraries/physics/src/ContactConstraint.h +++ b/libraries/physics/src/ContactConstraint.h @@ -1,6 +1,6 @@ // // ContactConstraint.h -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactPoint.cpp b/libraries/physics/src/ContactPoint.cpp index b9ad87aa8f..b949f12582 100644 --- a/libraries/physics/src/ContactPoint.cpp +++ b/libraries/physics/src/ContactPoint.cpp @@ -1,6 +1,6 @@ // // ContactPoint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.30 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactPoint.h b/libraries/physics/src/ContactPoint.h index d584945970..c2443f361f 100644 --- a/libraries/physics/src/ContactPoint.h +++ b/libraries/physics/src/ContactPoint.h @@ -1,6 +1,6 @@ // // ContactPoint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.30 // Copyright 2014 High Fidelity, Inc. @@ -15,7 +15,8 @@ #include #include -#include "CollisionInfo.h" +#include + #include "VerletPoint.h" class Shape; diff --git a/libraries/physics/src/DistanceConstraint.cpp b/libraries/physics/src/DistanceConstraint.cpp index 94dbfeba24..d513c4785b 100644 --- a/libraries/physics/src/DistanceConstraint.cpp +++ b/libraries/physics/src/DistanceConstraint.cpp @@ -1,6 +1,6 @@ // // DistanceConstraint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/DistanceConstraint.h b/libraries/physics/src/DistanceConstraint.h index c588807178..8c2dd38102 100644 --- a/libraries/physics/src/DistanceConstraint.h +++ b/libraries/physics/src/DistanceConstraint.h @@ -1,6 +1,6 @@ // // DistanceConstraint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/DoubleHashKey.cpp b/libraries/physics/src/DoubleHashKey.cpp new file mode 100644 index 0000000000..fae1d715fb --- /dev/null +++ b/libraries/physics/src/DoubleHashKey.cpp @@ -0,0 +1,43 @@ +// +// DoubleHashKey.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.02 +// 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 "DoubleHashKey.h" + +const int NUM_PRIMES = 64; +const unsigned int PRIMES[] = { + 4194301U, 4194287U, 4194277U, 4194271U, 4194247U, 4194217U, 4194199U, 4194191U, + 4194187U, 4194181U, 4194173U, 4194167U, 4194143U, 4194137U, 4194131U, 4194107U, + 4194103U, 4194023U, 4194011U, 4194007U, 4193977U, 4193971U, 4193963U, 4193957U, + 4193939U, 4193929U, 4193909U, 4193869U, 4193807U, 4193803U, 4193801U, 4193789U, + 4193759U, 4193753U, 4193743U, 4193701U, 4193663U, 4193633U, 4193573U, 4193569U, + 4193551U, 4193549U, 4193531U, 4193513U, 4193507U, 4193459U, 4193447U, 4193443U, + 4193417U, 4193411U, 4193393U, 4193389U, 4193381U, 4193377U, 4193369U, 4193359U, + 4193353U, 4193327U, 4193309U, 4193303U, 4193297U, 4193279U, 4193269U, 4193263U +}; + +unsigned int DoubleHashKey::hashFunction(unsigned int value, int primeIndex) { + unsigned int hash = PRIMES[primeIndex % NUM_PRIMES] * (value + 1U); + hash += ~(hash << 15); + hash ^= (hash >> 10); + hash += (hash << 3); + hash ^= (hash >> 6); + hash += ~(hash << 11); + return hash ^ (hash >> 16); +} + +unsigned int DoubleHashKey::hashFunction2(unsigned int value) { + unsigned hash = 0x811c9dc5U; + for (int i = 0; i < 4; i++ ) { + unsigned int byte = (value << (i * 8)) >> (24 - i * 8); + hash = ( hash ^ byte ) * 0x01000193U; + } + return hash; +} diff --git a/libraries/physics/src/DoubleHashKey.h b/libraries/physics/src/DoubleHashKey.h new file mode 100644 index 0000000000..ebacf6c96a --- /dev/null +++ b/libraries/physics/src/DoubleHashKey.h @@ -0,0 +1,38 @@ +// +// DoubleHashKey.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.02 +// 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_DoubleHashKey_h +#define hifi_DoubleHashKey_h + +// DoubleHashKey for use with btHashMap +class DoubleHashKey { +public: + static unsigned int hashFunction(unsigned int value, int primeIndex); + static unsigned int hashFunction2(unsigned int value); + + DoubleHashKey() : _hash(0), _hash2(0) { } + + DoubleHashKey(unsigned int value, int primeIndex = 0) : + _hash(hashFunction(value, primeIndex)), + _hash2(hashFunction2(value)) { + } + + bool equals(const DoubleHashKey& other) const { + return _hash == other._hash && _hash2 == other._hash2; + } + + unsigned int getHash() const { return (unsigned int)_hash; } + + int _hash; + int _hash2; +}; + +#endif // hifi_DoubleHashKey_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp new file mode 100644 index 0000000000..6e26f2b9de --- /dev/null +++ b/libraries/physics/src/EntityMotionState.cpp @@ -0,0 +1,172 @@ +// +// EntityMotionState.cpp +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.06 +// Copyright 2013 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 +#include + +#ifdef USE_BULLET_PHYSICS +#include "BulletUtil.h" +#endif // USE_BULLET_PHYSICS +#include "EntityMotionState.h" + +QSet* _outgoingEntityList; + +// static +void EntityMotionState::setOutgoingEntityList(QSet* list) { + assert(list); + _outgoingEntityList = list; +} + +// static +void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) { + assert(_outgoingEntityList); + _outgoingEntityList->insert(entity); +} + +EntityMotionState::EntityMotionState(EntityItem* entity) + : _entity(entity) { + assert(entity != NULL); +} + +EntityMotionState::~EntityMotionState() { + assert(_entity); + _entity->setPhysicsInfo(NULL); + _entity = NULL; +} + +MotionType EntityMotionState::computeMotionType() const { + // HACK: According to EntityTree the meaning of "static" is "not moving" whereas + // to Bullet it means "can't move". For demo purposes we temporarily interpret + // Entity::weightless to mean Bullet::static. + return _entity->getCollisionsWillMove() ? MOTION_TYPE_DYNAMIC : MOTION_TYPE_STATIC; +} + +#ifdef USE_BULLET_PHYSICS +// This callback is invoked by the physics simulation in two cases: +// (1) when the RigidBody is first added to the world +// (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC) +// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's -- +// it is an opportunity for outside code to update the object's simulation position +void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { + worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset())); + worldTrans.setRotation(glmToBullet(_entity->getRotation())); +} + +// This callback is invoked by the physics simulation at the end of each simulation frame... +// iff the corresponding RigidBody is DYNAMIC and has moved. +void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { + _entity->setPositionInMeters(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset()); + _entity->setRotation(bulletToGLM(worldTrans.getRotation())); + + glm::vec3 v; + getVelocity(v); + _entity->setVelocityInMeters(v); + + getAngularVelocity(v); + _entity->setAngularVelocity(v); + + _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; + EntityMotionState::enqueueOutgoingEntity(_entity); +} +#endif // USE_BULLET_PHYSICS + +void EntityMotionState::applyVelocities() const { +#ifdef USE_BULLET_PHYSICS + if (_body) { + setVelocity(_entity->getVelocityInMeters()); + setAngularVelocity(_entity->getAngularVelocity()); + _body->setActivationState(ACTIVE_TAG); + } +#endif // USE_BULLET_PHYSICS +} + +void EntityMotionState::applyGravity() const { +#ifdef USE_BULLET_PHYSICS + if (_body) { + setGravity(_entity->getGravityInMeters()); + _body->setActivationState(ACTIVE_TAG); + } +#endif // USE_BULLET_PHYSICS +} + +void EntityMotionState::computeShapeInfo(ShapeInfo& info) { + _entity->computeShapeInfo(info); +} + +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) { +#ifdef USE_BULLET_PHYSICS + if (_outgoingPacketFlags) { + EntityItemProperties properties = _entity->getProperties(); + + if (_outgoingPacketFlags & EntityItem::DIRTY_POSITION) { + btTransform worldTrans = _body->getWorldTransform(); + _sentPosition = bulletToGLM(worldTrans.getOrigin()); + properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset()); + + _sentRotation = bulletToGLM(worldTrans.getRotation()); + properties.setRotation(_sentRotation); + } + + if (_outgoingPacketFlags & EntityItem::DIRTY_VELOCITY) { + if (_body->isActive()) { + _sentVelocity = bulletToGLM(_body->getLinearVelocity()); + _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); + + // if the speeds are very small we zero them out + const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 4.0e-6f; // 2mm/sec + bool zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED); + if (zeroSpeed) { + _sentVelocity = glm::vec3(0.0f); + } + const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec + bool zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED; + if (zeroSpin) { + _sentAngularVelocity = glm::vec3(0.0f); + } + + _sentMoving = ! (zeroSpeed && zeroSpin); + } else { + _sentVelocity = _sentAngularVelocity = glm::vec3(0.0f); + _sentMoving = false; + } + properties.setVelocity(_sentVelocity); + _sentAcceleration = bulletToGLM(_body->getGravity()); + properties.setGravity(_sentAcceleration); + properties.setAngularVelocity(_sentAngularVelocity); + } + + // RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit. + if (_sentMoving) { + _numNonMovingUpdates = 0; + } else { + _numNonMovingUpdates++; + } + if (_numNonMovingUpdates <= 1) { + // we only update lastEdited when we're sending new physics data + // (i.e. NOT when we just simulate the positions forward, nore when we resend non-moving data) + quint64 lastSimulated = _entity->getLastSimulated(); + _entity->setLastEdited(lastSimulated); + properties.setLastEdited(lastSimulated); + } else { + properties.setLastEdited(_entity->getLastEdited()); + } + + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); + + // The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them + // to the full set. These flags may be momentarily cleared by incoming external changes. + _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; + _sentFrame = frame; + } +#endif // USE_BULLET_PHYSICS +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h new file mode 100644 index 0000000000..379470087f --- /dev/null +++ b/libraries/physics/src/EntityMotionState.h @@ -0,0 +1,71 @@ +// +// EntityMotionState.h +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.06 +// Copyright 2013 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_EntityMotionState_h +#define hifi_EntityMotionState_h + +#include + +#include "ObjectMotionState.h" +#ifndef USE_BULLET_PHYSICS +// ObjectMotionState stubbery +class ObjectMotionState { +public: + // so that this stub implementation is not completely empty we give the class a data member + bool _stubData; +}; +#endif // USE_BULLET_PHYSICS + +class EntityItem; + +// From the MotionState's perspective: +// Inside = physics simulation +// Outside = external agents (scripts, user interaction, other simulations) + +class EntityMotionState : public ObjectMotionState { +public: + + // The OutgoingEntityQueue is a pointer to a QSet (owned by an EntitySimulation) of EntityItem*'s + // that have been changed by the physics simulation. + // All ObjectMotionState's with outgoing changes put themselves on the list. + static void setOutgoingEntityList(QSet* list); + static void enqueueOutgoingEntity(EntityItem* entity); + + EntityMotionState(EntityItem* item); + virtual ~EntityMotionState(); + + /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem + MotionType computeMotionType() const; + +#ifdef USE_BULLET_PHYSICS + // this relays incoming position/rotation to the RigidBody + void getWorldTransform(btTransform& worldTrans) const; + + // this relays outgoing position/rotation to the EntityItem + void setWorldTransform(const btTransform& worldTrans); +#endif // USE_BULLET_PHYSICS + + // these relay incoming values to the RigidBody + void applyVelocities() const; + void applyGravity() const; + + void computeShapeInfo(ShapeInfo& info); + + void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame); + + uint32_t getIncomingDirtyFlags() const { return _entity->getDirtyFlags(); } + void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); } + +protected: + EntityItem* _entity; +}; + +#endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/FixedConstraint.cpp b/libraries/physics/src/FixedConstraint.cpp index 8f1edc1fb5..23f1749fa9 100644 --- a/libraries/physics/src/FixedConstraint.cpp +++ b/libraries/physics/src/FixedConstraint.cpp @@ -1,6 +1,6 @@ // // FixedConstraint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/FixedConstraint.h b/libraries/physics/src/FixedConstraint.h index 66a27369ce..067b9988cd 100644 --- a/libraries/physics/src/FixedConstraint.h +++ b/libraries/physics/src/FixedConstraint.h @@ -1,6 +1,6 @@ // // FixedConstraint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp new file mode 100644 index 0000000000..ded2291229 --- /dev/null +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -0,0 +1,173 @@ +// +// ObjectMotionState.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// 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 +// + +#ifdef USE_BULLET_PHYSICS + +#include + +#include "BulletUtil.h" +#include "ObjectMotionState.h" + +const float MIN_DENSITY = 200.0f; +const float DEFAULT_DENSITY = 1000.0f; +const float MAX_DENSITY = 20000.0f; + +const float MIN_VOLUME = 0.001f; +const float DEFAULT_VOLUME = 1.0f; +const float MAX_VOLUME = 1000000.0f; + +const float DEFAULT_FRICTION = 0.5f; +const float MAX_FRICTION = 10.0f; + +const float DEFAULT_RESTITUTION = 0.0f; + +// origin of physics simulation in world frame +glm::vec3 _worldOffset(0.0f); + +// static +void ObjectMotionState::setWorldOffset(const glm::vec3& offset) { + _worldOffset = offset; +} + +// static +const glm::vec3& ObjectMotionState::getWorldOffset() { + return _worldOffset; +} + + +ObjectMotionState::ObjectMotionState() : + _density(DEFAULT_DENSITY), + _volume(DEFAULT_VOLUME), + _friction(DEFAULT_FRICTION), + _restitution(DEFAULT_RESTITUTION), + _wasInWorld(false), + _motionType(MOTION_TYPE_STATIC), + _body(NULL), + _sentMoving(false), + _numNonMovingUpdates(0), + _outgoingPacketFlags(DIRTY_PHYSICS_FLAGS), + _sentFrame(0), + _sentPosition(0.0f), + _sentRotation(), + _sentVelocity(0.0f), + _sentAngularVelocity(0.0f), + _sentAcceleration(0.0f) { +} + +ObjectMotionState::~ObjectMotionState() { + // NOTE: you MUST remove this MotionState from the world before you call the dtor. + assert(_body == NULL); +} + +void ObjectMotionState::setDensity(float density) { + _density = btMax(btMin(fabsf(density), MAX_DENSITY), MIN_DENSITY); +} + +void ObjectMotionState::setFriction(float friction) { + _friction = btMax(btMin(fabsf(friction), MAX_FRICTION), 0.0f); +} + +void ObjectMotionState::setRestitution(float restitution) { + _restitution = btMax(btMin(fabsf(restitution), 1.0f), 0.0f); +} + +void ObjectMotionState::setVolume(float volume) { + _volume = btMax(btMin(fabsf(volume), MAX_VOLUME), MIN_VOLUME); +} + +void ObjectMotionState::setVelocity(const glm::vec3& velocity) const { + _body->setLinearVelocity(glmToBullet(velocity)); +} + +void ObjectMotionState::setAngularVelocity(const glm::vec3& velocity) const { + _body->setAngularVelocity(glmToBullet(velocity)); +} + +void ObjectMotionState::setGravity(const glm::vec3& gravity) const { + _body->setGravity(glmToBullet(gravity)); +} + +void ObjectMotionState::getVelocity(glm::vec3& velocityOut) const { + velocityOut = bulletToGLM(_body->getLinearVelocity()); +} + +void ObjectMotionState::getAngularVelocity(glm::vec3& angularVelocityOut) const { + angularVelocityOut = bulletToGLM(_body->getAngularVelocity()); +} + +// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates +// we alwasy resend packets for objects that have stopped moving up to some max limit. +const int MAX_NUM_NON_MOVING_UPDATES = 5; + +bool ObjectMotionState::doesNotNeedToSendUpdate() const { + return !_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES; +} + +const float FIXED_SUBSTEP = 1.0f / 60.0f; + +bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame, float subStepRemainder) const { + assert(_body); + float dt = (float)(simulationFrame - _sentFrame) * FIXED_SUBSTEP + subStepRemainder; + bool isActive = _body->isActive(); + + if (isActive) { + const float MAX_UPDATE_PERIOD_FOR_ACTIVE_THINGS = 10.0f; + if (dt > MAX_UPDATE_PERIOD_FOR_ACTIVE_THINGS) { + return true; + } + } else if (_sentMoving) { + if (!isActive) { + // this object just went inactive so send an update immediately + return true; + } + } else { + const float NON_MOVING_UPDATE_PERIOD = 1.0f; + if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) { + // RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these + // at a faster rate than the MAX period above, and only send a limited number of them. + return true; + } + } + + // Else we measure the error between current and extrapolated transform (according to expected behavior + // of remote EntitySimulation) and return true if the error is significant. + + // NOTE: math in done the simulation-frame, which is NOT necessarily the same as the world-frame + // due to _worldOffset. + + // TODO: Andrew to reconcile Bullet and legacy damping coefficients. + + // compute position error + glm::vec3 extrapolatedPosition = _sentPosition + dt * (_sentVelocity + (0.5f * dt) * _sentAcceleration); + + btTransform worldTrans = _body->getWorldTransform(); + glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); + + float dx2 = glm::distance2(position, extrapolatedPosition); + const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m + if (dx2 > MAX_POSITION_ERROR_SQUARED) { + return true; + } + + // compute rotation error + float spin = glm::length(_sentAngularVelocity); + glm::quat extrapolatedRotation = _sentRotation; + const float MIN_SPIN = 1.0e-4f; + if (spin > MIN_SPIN) { + glm::vec3 axis = _sentAngularVelocity / spin; + extrapolatedRotation = glm::angleAxis(dt * spin, axis) * _sentRotation; + } + const float MIN_ROTATION_DOT = 0.98f; + glm::quat actualRotation = bulletToGLM(worldTrans.getRotation()); + return (glm::dot(actualRotation, extrapolatedRotation) < MIN_ROTATION_DOT); +} + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h new file mode 100644 index 0000000000..ea9dcad0fc --- /dev/null +++ b/libraries/physics/src/ObjectMotionState.h @@ -0,0 +1,115 @@ +// +// ObjectMotionState.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// 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_ObjectMotionState_h +#define hifi_ObjectMotionState_h + +#include + +enum MotionType { + MOTION_TYPE_STATIC, // no motion + MOTION_TYPE_DYNAMIC, // motion according to physical laws + MOTION_TYPE_KINEMATIC // keyframed motion +}; + +// The update flags trigger two varieties of updates: "hard" which require the body to be pulled +// and re-added to the physics engine and "easy" which just updates the body properties. +typedef unsigned int uint32_t; +const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE); +const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY | + EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP); + +// These are the set of incoming flags that the PhysicsEngine needs to hear about: +const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS; + +// These are the outgoing flags that the PhysicsEngine can affect: +const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY; + +#ifdef USE_BULLET_PHYSICS + +#include +#include +#include // for EntityItem::DIRTY_FOO bitmasks + +#include "ShapeInfo.h" + +class OctreeEditPacketSender; + +class ObjectMotionState : public btMotionState { +public: + // The WorldOffset is used to keep the positions of objects in the simulation near the origin, to + // reduce numerical error when computing vector differences. In other words: The EntityMotionState + // class translates between the simulation-frame and the world-frame as known by the render pipeline, + // various object trees, etc. The EntityMotionState class uses a static "worldOffset" to help in + // the translations. + static void setWorldOffset(const glm::vec3& offset); + static const glm::vec3& getWorldOffset(); + + ObjectMotionState(); + ~ObjectMotionState(); + + virtual void applyVelocities() const = 0; + virtual void applyGravity() const = 0; + + virtual void computeShapeInfo(ShapeInfo& info) = 0; + + virtual MotionType getMotionType() const { return _motionType; } + + void setDensity(float density); + void setFriction(float friction); + void setRestitution(float restitution); + void setVolume(float volume); + + float getMass() const { return _volume * _density; } + + void setVelocity(const glm::vec3& velocity) const; + void setAngularVelocity(const glm::vec3& velocity) const; + void setGravity(const glm::vec3& gravity) const; + + void getVelocity(glm::vec3& velocityOut) const; + void getAngularVelocity(glm::vec3& angularVelocityOut) const; + + virtual uint32_t getIncomingDirtyFlags() const = 0; + virtual void clearIncomingDirtyFlags(uint32_t flags) = 0; + void clearOutgoingPacketFlags(uint32_t flags) { _outgoingPacketFlags &= ~flags; } + + bool doesNotNeedToSendUpdate() const; + virtual bool shouldSendUpdate(uint32_t simulationFrame, float subStepRemainder) const; + virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0; + + virtual MotionType computeMotionType() const = 0; + + friend class PhysicsEngine; +protected: + float _density; + float _volume; + float _friction; + float _restitution; + bool _wasInWorld; + MotionType _motionType; + + // _body has NO setters -- it is only changed by PhysicsEngine + btRigidBody* _body; + + bool _sentMoving; // true if last update was moving + int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects + + uint32_t _outgoingPacketFlags; + uint32_t _sentFrame; + glm::vec3 _sentPosition; // in simulation-frame (not world-frame) + glm::quat _sentRotation;; + glm::vec3 _sentVelocity; + glm::vec3 _sentAngularVelocity; + glm::vec3 _sentAcceleration; +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_ObjectMotionState_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp new file mode 100644 index 0000000000..a6317b4fcf --- /dev/null +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -0,0 +1,454 @@ +// +// PhysicsEngine.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// 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 "PhysicsEngine.h" +#ifdef USE_BULLET_PHYSICS + +#include "ShapeInfoUtil.h" +#include "ThreadSafeDynamicsWorld.h" + +class EntityTree; + +PhysicsEngine::PhysicsEngine(const glm::vec3& offset) + : _collisionConfig(NULL), + _collisionDispatcher(NULL), + _broadphaseFilter(NULL), + _constraintSolver(NULL), + _dynamicsWorld(NULL), + _originOffset(offset), + _voxels(), + _entityPacketSender(NULL), + _frameCount(0) { +} + +PhysicsEngine::~PhysicsEngine() { +} + +// begin EntitySimulation overrides +void PhysicsEngine::updateEntitiesInternal(const quint64& now) { + // NOTE: the grand order of operations is: + // (1) relay incoming changes + // (2) step simulation + // (3) synchronize outgoing motion states + // (4) send outgoing packets + + // this is step (4) + QSet::iterator stateItr = _outgoingPackets.begin(); + uint32_t frame = getFrameCount(); + float subStepRemainder = getSubStepRemainder(); + while (stateItr != _outgoingPackets.end()) { + ObjectMotionState* state = *stateItr; + if (state->doesNotNeedToSendUpdate()) { + stateItr = _outgoingPackets.erase(stateItr); + } else if (state->shouldSendUpdate(frame, subStepRemainder)) { + state->sendUpdate(_entityPacketSender, frame); + ++stateItr; + } else { + ++stateItr; + } + } +} + +void PhysicsEngine::addEntityInternal(EntityItem* entity) { + assert(entity); + void* physicsInfo = entity->getPhysicsInfo(); + if (!physicsInfo) { + EntityMotionState* motionState = new EntityMotionState(entity); + if (addObject(motionState)) { + entity->setPhysicsInfo(static_cast(motionState)); + _entityMotionStates.insert(motionState); + } else { + // We failed to add the entity to the simulation. Probably because we couldn't create a shape for it. + qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine"; + delete motionState; + } + } +} + +void PhysicsEngine::removeEntityInternal(EntityItem* entity) { + assert(entity); + void* physicsInfo = entity->getPhysicsInfo(); + if (physicsInfo) { + EntityMotionState* motionState = static_cast(physicsInfo); + removeObject(motionState); + _entityMotionStates.remove(motionState); + _incomingChanges.remove(motionState); + _outgoingPackets.remove(motionState); + delete motionState; + } +} + +void PhysicsEngine::entityChangedInternal(EntityItem* entity) { + // queue incoming changes: from external sources (script, EntityServer, etc) to physics engine + assert(entity); + void* physicsInfo = entity->getPhysicsInfo(); + if (physicsInfo) { + ObjectMotionState* motionState = static_cast(physicsInfo); + _incomingChanges.insert(motionState); + } else { + // try to add this entity again (maybe something changed such that it will work this time) + addEntity(entity); + } +} + +void PhysicsEngine::sortEntitiesThatMovedInternal() { + // entities that have been simulated forward (hence in the _entitiesToBeSorted list) + // also need to be put in the outgoingPackets list + QSet::iterator entityItr = _entitiesToBeSorted.begin(); + while (entityItr != _entitiesToBeSorted.end()) { + EntityItem* entity = *entityItr; + void* physicsInfo = entity->getPhysicsInfo(); + assert(physicsInfo); + ObjectMotionState* motionState = static_cast(physicsInfo); + _outgoingPackets.insert(motionState); + ++entityItr; + } +} + +void PhysicsEngine::clearEntitiesInternal() { + // For now we assume this would only be called on shutdown in which case we can just let the memory get lost. + QSet::const_iterator stateItr = _entityMotionStates.begin(); + for (stateItr = _entityMotionStates.begin(); stateItr != _entityMotionStates.end(); ++stateItr) { + removeObject(*stateItr); + delete (*stateItr); + } + _entityMotionStates.clear(); + _incomingChanges.clear(); + _outgoingPackets.clear(); +} +// end EntitySimulation overrides + +void PhysicsEngine::relayIncomingChangesToSimulation() { + // process incoming changes + QSet::iterator stateItr = _incomingChanges.begin(); + while (stateItr != _incomingChanges.end()) { + ObjectMotionState* motionState = *stateItr; + uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; + + btRigidBody* body = motionState->_body; + if (body) { + if (flags & HARD_DIRTY_PHYSICS_FLAGS) { + // a HARD update requires the body be pulled out of physics engine, changed, then reinserted + // but it also handles all EASY changes + updateObjectHard(body, motionState, flags); + } else if (flags) { + // an EASY update does NOT require that the body be pulled out of physics engine + updateObjectEasy(body, motionState, flags); + } + } + + // NOTE: the grand order of operations is: + // (1) relay incoming changes + // (2) step simulation + // (3) synchronize outgoing motion states + // (4) send outgoing packets + // + // We're in the middle of step (1) hence incoming changes should trump corresponding + // outgoing changes at this point. + motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped + motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed + ++stateItr; + } + _incomingChanges.clear(); +} + +// virtual +void PhysicsEngine::init(EntityEditPacketSender* packetSender) { + // _entityTree should be set prior to the init() call + assert(_entityTree); + + if (!_dynamicsWorld) { + _collisionConfig = new btDefaultCollisionConfiguration(); + _collisionDispatcher = new btCollisionDispatcher(_collisionConfig); + _broadphaseFilter = new btDbvtBroadphase(); + _constraintSolver = new btSequentialImpulseConstraintSolver; + _dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig, _entityTree); + + // default gravity of the world is zero, so each object must specify its own gravity + // TODO: set up gravity zones + _dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f)); + + // GROUND HACK: add a big planar floor (and walls for testing) to catch falling objects + btTransform groundTransform; + groundTransform.setIdentity(); + for (int i = 0; i < 3; ++i) { + btVector3 normal(0.0f, 0.0f, 0.0f); + normal[i] = 1.0f; + btCollisionShape* plane = new btStaticPlaneShape(normal, 0.0f); + + btCollisionObject* groundObject = new btCollisionObject(); + groundObject->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); + groundObject->setCollisionShape(plane); + + groundObject->setWorldTransform(groundTransform); + _dynamicsWorld->addCollisionObject(groundObject); + } + } + + assert(packetSender); + _entityPacketSender = packetSender; + EntityMotionState::setOutgoingEntityList(&_entitiesToBeSorted); +} + +const float FIXED_SUBSTEP = 1.0f / 60.0f; + +void PhysicsEngine::stepSimulation() { + // NOTE: the grand order of operations is: + // (1) relay incoming changes + // (2) step simulation + // (3) synchronize outgoing motion states + // (4) send outgoing packets + + // this is step (1) + relayIncomingChangesToSimulation(); + + const int MAX_NUM_SUBSTEPS = 4; + const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * FIXED_SUBSTEP; + float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds()); + _clock.reset(); + float timeStep = btMin(dt, MAX_TIMESTEP); + + // steps (2) and (3) are performed by ThreadSafeDynamicsWorld::stepSimulation()) + int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, FIXED_SUBSTEP); + _frameCount += (uint32_t)numSubSteps; +} + +bool PhysicsEngine::addVoxel(const glm::vec3& position, float scale) { + glm::vec3 halfExtents = glm::vec3(0.5f * scale); + glm::vec3 trueCenter = position + halfExtents; + PositionHashKey key(trueCenter); + VoxelObject* proxy = _voxels.find(key); + if (!proxy) { + // create a shape + ShapeInfo info; + info.setBox(halfExtents); + btCollisionShape* shape = _shapeManager.getShape(info); + + // NOTE: the shape creation will fail when the size of the voxel is out of range + if (shape) { + // create a collisionObject + btCollisionObject* object = new btCollisionObject(); + object->setCollisionShape(shape); + btTransform transform; + transform.setIdentity(); + // we shift the center into the simulation's frame + glm::vec3 shiftedCenter = (position - _originOffset) + halfExtents; + transform.setOrigin(btVector3(shiftedCenter.x, shiftedCenter.y, shiftedCenter.z)); + object->setWorldTransform(transform); + + // add to map and world + _voxels.insert(key, VoxelObject(trueCenter, object)); + _dynamicsWorld->addCollisionObject(object); + return true; + } + } + return false; +} + +bool PhysicsEngine::removeVoxel(const glm::vec3& position, float scale) { + glm::vec3 halfExtents = glm::vec3(0.5f * scale); + glm::vec3 trueCenter = position + halfExtents; + PositionHashKey key(trueCenter); + VoxelObject* proxy = _voxels.find(key); + if (proxy) { + // remove from world + assert(proxy->_object); + _dynamicsWorld->removeCollisionObject(proxy->_object); + + // release shape + ShapeInfo info; + info.setBox(halfExtents); + bool released = _shapeManager.releaseShape(info); + assert(released); + + // delete object and remove from voxel map + delete proxy->_object; + _voxels.remove(key); + return true; + } + return false; +} + +// Bullet collision flags are as follows: +// CF_STATIC_OBJECT= 1, +// CF_KINEMATIC_OBJECT= 2, +// CF_NO_CONTACT_RESPONSE = 4, +// CF_CUSTOM_MATERIAL_CALLBACK = 8,//this allows per-triangle material (friction/restitution) +// CF_CHARACTER_OBJECT = 16, +// CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing +// CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing + +bool PhysicsEngine::addObject(ObjectMotionState* motionState) { + assert(motionState); + ShapeInfo info; + motionState->computeShapeInfo(info); + btCollisionShape* shape = _shapeManager.getShape(info); + if (shape) { + btVector3 inertia(0.0f, 0.0f, 0.0f); + float mass = 0.0f; + btRigidBody* body = NULL; + switch(motionState->computeMotionType()) { + case MOTION_TYPE_KINEMATIC: { + body = new btRigidBody(mass, motionState, shape, inertia); + body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + body->setActivationState(DISABLE_DEACTIVATION); + body->updateInertiaTensor(); + motionState->_body = body; + break; + } + case MOTION_TYPE_DYNAMIC: { + mass = motionState->getMass(); + shape->calculateLocalInertia(mass, inertia); + body = new btRigidBody(mass, motionState, shape, inertia); + body->updateInertiaTensor(); + motionState->_body = body; + motionState->applyVelocities(); + motionState->applyGravity(); + break; + } + case MOTION_TYPE_STATIC: + default: { + body = new btRigidBody(mass, motionState, shape, inertia); + body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); + body->updateInertiaTensor(); + motionState->_body = body; + break; + } + } + // wtf? + body->setFlags(BT_DISABLE_WORLD_GRAVITY); + body->setRestitution(motionState->_restitution); + body->setFriction(motionState->_friction); + _dynamicsWorld->addRigidBody(body); + return true; + } + return false; +} + +bool PhysicsEngine::removeObject(ObjectMotionState* motionState) { + assert(motionState); + btRigidBody* body = motionState->_body; + if (body) { + const btCollisionShape* shape = body->getCollisionShape(); + ShapeInfo info; + ShapeInfoUtil::collectInfoFromShape(shape, info); + _dynamicsWorld->removeRigidBody(body); + _shapeManager.releaseShape(info); + delete body; + motionState->_body = NULL; + return true; + } + return false; +} + +// private +void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { + MotionType newType = motionState->computeMotionType(); + + // pull body out of physics engine + _dynamicsWorld->removeRigidBody(body); + + if (flags & EntityItem::DIRTY_SHAPE) { + btCollisionShape* oldShape = body->getCollisionShape(); + ShapeInfo info; + motionState->computeShapeInfo(info); + btCollisionShape* newShape = _shapeManager.getShape(info); + if (newShape != oldShape) { + body->setCollisionShape(newShape); + _shapeManager.releaseShape(oldShape); + } else { + // whoops, shape hasn't changed after all so we must release the reference + // that was created when looking it up + _shapeManager.releaseShape(newShape); + } + // MASS bit should be set whenever SHAPE is set + assert(flags & EntityItem::DIRTY_MASS); + } + bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS; + if (easyUpdate) { + updateObjectEasy(body, motionState, flags); + } + + // update the motion parameters + switch (newType) { + case MOTION_TYPE_KINEMATIC: { + int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT; + collisionFlags &= ~(btCollisionObject::CF_STATIC_OBJECT); + body->setCollisionFlags(collisionFlags); + body->forceActivationState(DISABLE_DEACTIVATION); + + body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f)); + body->updateInertiaTensor(); + break; + } + case MOTION_TYPE_DYNAMIC: { + int collisionFlags = body->getCollisionFlags() & ~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT); + body->setCollisionFlags(collisionFlags); + if (! (flags & EntityItem::DIRTY_MASS)) { + // always update mass properties when going dynamic (unless it's already been done) + btVector3 inertia(0.0f, 0.0f, 0.0f); + float mass = motionState->getMass(); + body->getCollisionShape()->calculateLocalInertia(mass, inertia); + body->setMassProps(mass, inertia); + body->updateInertiaTensor(); + } + body->forceActivationState(ACTIVE_TAG); + break; + } + default: { + // MOTION_TYPE_STATIC + int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT; + collisionFlags &= ~(btCollisionObject::CF_KINEMATIC_OBJECT); + body->setCollisionFlags(collisionFlags); + body->forceActivationState(DISABLE_SIMULATION); + + body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f)); + body->updateInertiaTensor(); + + body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f)); + body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f)); + break; + } + } + + // reinsert body into physics engine + _dynamicsWorld->addRigidBody(body); + + body->activate(); +} + +// private +void PhysicsEngine::updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { + if (flags & EntityItem::DIRTY_POSITION) { + btTransform transform; + motionState->getWorldTransform(transform); + body->setWorldTransform(transform); + } + if (flags & EntityItem::DIRTY_VELOCITY) { + motionState->applyVelocities(); + motionState->applyGravity(); + } + body->setRestitution(motionState->_restitution); + body->setFriction(motionState->_friction); + + if (flags & EntityItem::DIRTY_MASS) { + float mass = motionState->getMass(); + btVector3 inertia(0.0f, 0.0f, 0.0f); + body->getCollisionShape()->calculateLocalInertia(mass, inertia); + body->setMassProps(mass, inertia); + body->updateInertiaTensor(); + } + body->activate(); + + // TODO: support collision groups +}; + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h new file mode 100644 index 0000000000..5d506fced6 --- /dev/null +++ b/libraries/physics/src/PhysicsEngine.h @@ -0,0 +1,123 @@ +// +// PhysicsEngine.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// 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_PhysicsEngine_h +#define hifi_PhysicsEngine_h + +typedef unsigned int uint32_t; + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +#include +#include + +#include "BulletUtil.h" +#include "EntityMotionState.h" +#include "PositionHashKey.h" +#include "ShapeManager.h" +#include "ThreadSafeDynamicsWorld.h" +#include "VoxelObject.h" + +const float HALF_SIMULATION_EXTENT = 512.0f; // meters + +class PhysicsEngine : public EntitySimulation { +public: + + PhysicsEngine(const glm::vec3& offset); + + ~PhysicsEngine(); + + // overrides for EntitySimulation + void updateEntitiesInternal(const quint64& now); + void addEntityInternal(EntityItem* entity); + void removeEntityInternal(EntityItem* entity); + void entityChangedInternal(EntityItem* entity); + void sortEntitiesThatMovedInternal(); + void clearEntitiesInternal(); + + virtual void init(EntityEditPacketSender* packetSender); + + void stepSimulation(); + + /// \param offset position of simulation origin in domain-frame + void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; } + + /// \return position of simulation origin in domain-frame + const glm::vec3& getOriginOffset() const { return _originOffset; } + + /// \param position the minimum corner of the voxel + /// \param scale the length of the voxel side + /// \return true if Voxel added + bool addVoxel(const glm::vec3& position, float scale); + + /// \param position the minimum corner of the voxel + /// \param scale the length of the voxel side + /// \return true if Voxel removed + bool removeVoxel(const glm::vec3& position, float scale); + + /// \param motionState pointer to Object's MotionState + /// \return true if Object added + bool addObject(ObjectMotionState* motionState); + + /// \param motionState pointer to Object's MotionState + /// \return true if Object removed + bool removeObject(ObjectMotionState* motionState); + + /// process queue of changed from external sources + void relayIncomingChangesToSimulation(); + + /// \return duration of fixed simulation substep + float getFixedSubStep() const; + + /// \return number of simulation frames the physics engine has taken + uint32_t getFrameCount() const { return _frameCount; } + + /// \return substep remainder used for Bullet MotionState extrapolation + // Bullet will extrapolate the positions provided to MotionState::setWorldTransform() in an effort to provide + // smoother visible motion when the render frame rate does not match that of the simulation loop. We provide + // access to this fraction for improved filtering of update packets to interested parties. + float getSubStepRemainder() { return _dynamicsWorld->getLocalTimeAccumulation(); } + +protected: + void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); + void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); + + btClock _clock; + btDefaultCollisionConfiguration* _collisionConfig; + btCollisionDispatcher* _collisionDispatcher; + btBroadphaseInterface* _broadphaseFilter; + btSequentialImpulseConstraintSolver* _constraintSolver; + ThreadSafeDynamicsWorld* _dynamicsWorld; + ShapeManager _shapeManager; + +private: + glm::vec3 _originOffset; + btHashMap _voxels; + + // EntitySimulation stuff + QSet _entityMotionStates; // all entities that we track + QSet _incomingChanges; // entities with pending physics changes by script or packet + QSet _outgoingPackets; // MotionStates with pending changes that need to be sent over wire + + EntityEditPacketSender* _entityPacketSender; + + uint32_t _frameCount; +}; + +#else // USE_BULLET_PHYSICS +// PhysicsEngine stubbery until Bullet is required +class PhysicsEngine { +}; +#endif // USE_BULLET_PHYSICS +#endif // hifi_PhysicsEngine_h diff --git a/libraries/physics/src/PhysicsEntity.cpp b/libraries/physics/src/PhysicsEntity.cpp index a01706f539..80176edb76 100644 --- a/libraries/physics/src/PhysicsEntity.cpp +++ b/libraries/physics/src/PhysicsEntity.cpp @@ -1,6 +1,6 @@ // // PhysicsEntity.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.06.11 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicsEntity.h b/libraries/physics/src/PhysicsEntity.h index a96754b75c..92b302debb 100644 --- a/libraries/physics/src/PhysicsEntity.h +++ b/libraries/physics/src/PhysicsEntity.h @@ -1,6 +1,6 @@ // // PhysicsEntity.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.05.30 // Copyright 2014 High Fidelity, Inc. @@ -18,8 +18,12 @@ #include #include -#include "CollisionInfo.h" -#include "RayIntersectionInfo.h" +#include +#include + +#ifdef USE_BULLET_PHYSICS +#include "PhysicsEngine.h" +#endif // USE_BULLET_PHYSICS class Shape; class PhysicsSimulation; diff --git a/libraries/physics/src/PhysicsSimulation.cpp b/libraries/physics/src/PhysicsSimulation.cpp index 72a1eeebfd..b92d9bec8f 100644 --- a/libraries/physics/src/PhysicsSimulation.cpp +++ b/libraries/physics/src/PhysicsSimulation.cpp @@ -1,6 +1,6 @@ // // PhysicsSimulation.cpp -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.06.06 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicsSimulation.h b/libraries/physics/src/PhysicsSimulation.h index 12506e23d0..90d6e38187 100644 --- a/libraries/physics/src/PhysicsSimulation.h +++ b/libraries/physics/src/PhysicsSimulation.h @@ -1,6 +1,6 @@ // // PhysicsSimulation.h -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.06.06 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PositionHashKey.cpp b/libraries/physics/src/PositionHashKey.cpp new file mode 100644 index 0000000000..91f8261fba --- /dev/null +++ b/libraries/physics/src/PositionHashKey.cpp @@ -0,0 +1,37 @@ +// +// PositionHashKey.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// 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 + +#include "PositionHashKey.h" + +// static +int computeHash(const glm::vec3& center) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + int hash = DoubleHashKey::hashFunction((int)(center.x * MILLIMETERS_PER_METER + copysignf(1.0f, center.x) * 0.49f), 0); + hash ^= DoubleHashKey::hashFunction((int)(center.y * MILLIMETERS_PER_METER + copysignf(1.0f, center.y) * 0.49f), 1); + return hash ^ DoubleHashKey::hashFunction((int)(center.z * MILLIMETERS_PER_METER + copysignf(1.0f, center.z) * 0.49f), 2); +} + +// static +int computeHash2(const glm::vec3& center) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + int hash = DoubleHashKey::hashFunction2((int)(center.x * MILLIMETERS_PER_METER + copysignf(1.0f, center.x) * 0.49f)); + hash ^= DoubleHashKey::hashFunction2((int)(center.y * MILLIMETERS_PER_METER + copysignf(1.0f, center.y) * 0.49f)); + return hash ^ DoubleHashKey::hashFunction2((int)(center.z * MILLIMETERS_PER_METER + copysignf(1.0f, center.z) * 0.49f)); +} + +PositionHashKey::PositionHashKey(glm::vec3 center) : DoubleHashKey() { + _hash = computeHash(center); + _hash2 = computeHash2(center); +} diff --git a/libraries/physics/src/PositionHashKey.h b/libraries/physics/src/PositionHashKey.h new file mode 100644 index 0000000000..1065034fa8 --- /dev/null +++ b/libraries/physics/src/PositionHashKey.h @@ -0,0 +1,26 @@ +// +// PositionHashKey.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// 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_PositionHashKey_h +#define hifi_PositionHashKey_h + +#include + +#include + +#include "DoubleHashKey.h" + +class PositionHashKey : public DoubleHashKey { +public: + PositionHashKey(glm::vec3 center); +}; + +#endif // hifi_PositionHashKey_h diff --git a/libraries/physics/src/Ragdoll.cpp b/libraries/physics/src/Ragdoll.cpp index 3eecef2333..7936828025 100644 --- a/libraries/physics/src/Ragdoll.cpp +++ b/libraries/physics/src/Ragdoll.cpp @@ -1,6 +1,6 @@ // // Ragdoll.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.05.30 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/Ragdoll.h b/libraries/physics/src/Ragdoll.h index 5447b6769e..ab81f165bd 100644 --- a/libraries/physics/src/Ragdoll.h +++ b/libraries/physics/src/Ragdoll.h @@ -1,6 +1,6 @@ // // Ragdoll.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.05.30 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ShapeInfoUtil.cpp b/libraries/physics/src/ShapeInfoUtil.cpp new file mode 100644 index 0000000000..f562201f73 --- /dev/null +++ b/libraries/physics/src/ShapeInfoUtil.cpp @@ -0,0 +1,172 @@ +// +// ShapeInfoUtil.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.12.01 +// 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 // for FOO_SHAPE types +#include // for MILLIMETERS_PER_METER + +#include "ShapeInfoUtil.h" +#include "BulletUtil.h" + +#ifdef USE_BULLET_PHYSICS + + +int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) { + int bulletShapeType = INVALID_SHAPE_PROXYTYPE; + switch(shapeInfoType) { + case BOX_SHAPE: + bulletShapeType = BOX_SHAPE_PROXYTYPE; + break; + case SPHERE_SHAPE: + bulletShapeType = SPHERE_SHAPE_PROXYTYPE; + break; + case CAPSULE_SHAPE: + bulletShapeType = CAPSULE_SHAPE_PROXYTYPE; + break; + case CYLINDER_SHAPE: + bulletShapeType = CYLINDER_SHAPE_PROXYTYPE; + break; + } + return bulletShapeType; +} + +int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) { + int shapeInfoType = INVALID_SHAPE; + switch(bulletShapeType) { + case BOX_SHAPE_PROXYTYPE: + shapeInfoType = BOX_SHAPE; + break; + case SPHERE_SHAPE_PROXYTYPE: + shapeInfoType = SPHERE_SHAPE; + break; + case CAPSULE_SHAPE_PROXYTYPE: + shapeInfoType = CAPSULE_SHAPE; + break; + case CYLINDER_SHAPE_PROXYTYPE: + shapeInfoType = CYLINDER_SHAPE; + break; + } + return shapeInfoType; +} + +void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info) { + if (shape) { + int type = ShapeInfoUtil::fromBulletShapeType(shape->getShapeType()); + switch(type) { + case BOX_SHAPE: { + const btBoxShape* boxShape = static_cast(shape); + info.setBox(bulletToGLM(boxShape->getHalfExtentsWithMargin())); + } + break; + case SPHERE_SHAPE: { + const btSphereShape* sphereShape = static_cast(shape); + info.setSphere(sphereShape->getRadius()); + } + break; + case CYLINDER_SHAPE: { + // NOTE: we only support cylinders along yAxis + const btCylinderShape* cylinderShape = static_cast(shape); + btVector3 halfExtents = cylinderShape->getHalfExtentsWithMargin(); + info.setCylinder(halfExtents.getX(), halfExtents.getY()); + } + break; + case CAPSULE_SHAPE: { + // NOTE: we only support capsules along yAxis + const btCapsuleShape* capsuleShape = static_cast(shape); + info.setCapsule(capsuleShape->getRadius(), capsuleShape->getHalfHeight()); + } + break; + default: + info.clear(); + break; + } + } else { + info.clear(); + } +} + +btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) { + btCollisionShape* shape = NULL; + const QVector& data = info.getData(); + switch(info.getType()) { + case BOX_SHAPE: { + // data[0] is halfExtents + shape = new btBoxShape(glmToBullet(data[0])); + } + break; + case SPHERE_SHAPE: { + float radius = data[0].z; + shape = new btSphereShape(radius); + } + break; + case CYLINDER_SHAPE: { + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // data[0] = btVector3(radius, halfHeight, unused) + shape = new btCylinderShape(glmToBullet(data[0])); + } + break; + case CAPSULE_SHAPE: { + float radius = data[0].x; + float height = 2.0f * data[0].y; + shape = new btCapsuleShape(radius, height); + } + break; + } + return shape; +} + +DoubleHashKey ShapeInfoUtil::computeHash(const ShapeInfo& info) { + DoubleHashKey key; + // compute hash + // scramble the bits of the type + // TODO?: provide lookup table for hash of info._type rather than recompute? + int primeIndex = 0; + unsigned int hash = DoubleHashKey::hashFunction((unsigned int)info.getType(), primeIndex++); + const QVector& data = info.getData(); + + glm::vec3 tmpData; + int numData = data.size(); + for (int i = 0; i < numData; ++i) { + tmpData = data[i]; + for (int j = 0; j < 3; ++j) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + unsigned int floatHash = + DoubleHashKey::hashFunction((int)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f), primeIndex++); + hash ^= floatHash; + } + } + key._hash = (int)hash; + + // compute hash2 + // scramble the bits of the type + // TODO?: provide lookup table for hash2 of info._type rather than recompute? + hash = DoubleHashKey::hashFunction2((unsigned int)info.getType()); + + for (int i = 0; i < numData; ++i) { + tmpData = data[i]; + for (int j = 0; j < 3; ++j) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + unsigned int floatHash = + DoubleHashKey::hashFunction2((int)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f)); + hash += ~(floatHash << 17); + hash ^= (floatHash >> 11); + hash += (floatHash << 4); + hash ^= (floatHash >> 7); + hash += ~(floatHash << 10); + hash = (hash << 16) | (hash >> 16); + } + } + key._hash2 = (int)hash; + return key; +} + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ShapeInfoUtil.h b/libraries/physics/src/ShapeInfoUtil.h new file mode 100644 index 0000000000..7363cf483b --- /dev/null +++ b/libraries/physics/src/ShapeInfoUtil.h @@ -0,0 +1,39 @@ +// +// ShapeInfoUtil.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.12.01 +// 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_ShapeInfoUtil_h +#define hifi_ShapeInfoUtil_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +#include + +#include "DoubleHashKey.h" + +// translates between ShapeInfo and btShape + +namespace ShapeInfoUtil { + void collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info); + + btCollisionShape* createShapeFromInfo(const ShapeInfo& info); + + DoubleHashKey computeHash(const ShapeInfo& info); + + // TODO? just use bullet shape types everywhere? + int toBulletShapeType(int shapeInfoType); + int fromBulletShapeType(int bulletShapeType); +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_ShapeInfoUtil_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp new file mode 100644 index 0000000000..1da705a5ff --- /dev/null +++ b/libraries/physics/src/ShapeManager.cpp @@ -0,0 +1,109 @@ +// +// ShapeManager.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// 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 +// + +#ifdef USE_BULLET_PHYSICS + +#include + +#include "ShapeInfoUtil.h" +#include "ShapeManager.h" + +ShapeManager::ShapeManager() { +} + +ShapeManager::~ShapeManager() { + int numShapes = _shapeMap.size(); + for (int i = 0; i < numShapes; ++i) { + ShapeReference* shapeRef = _shapeMap.getAtIndex(i); + delete shapeRef->_shape; + } + _shapeMap.clear(); +} + +btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { + // Very small or large objects are not supported. + float diagonal = glm::length2(info.getBoundingBoxDiagonal()); + const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube + const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e4f; // 100 m cube + if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED || diagonal > MAX_SHAPE_DIAGONAL_SQUARED) { + return NULL; + } + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef) { + shapeRef->_refCount++; + return shapeRef->_shape; + } + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + if (shape) { + ShapeReference newRef; + newRef._refCount = 1; + newRef._shape = shape; + _shapeMap.insert(key, newRef); + } + return shape; +} + +bool ShapeManager::releaseShape(const ShapeInfo& info) { + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef) { + if (shapeRef->_refCount > 0) { + shapeRef->_refCount--; + if (shapeRef->_refCount == 0) { + _pendingGarbage.push_back(key); + const int MAX_GARBAGE_CAPACITY = 127; + if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) { + collectGarbage(); + } + } + return true; + } else { + // attempt to remove shape that has no refs + assert(false); + } + } else { + // attempt to remove unmanaged shape + assert(false); + } + return false; +} + +bool ShapeManager::releaseShape(const btCollisionShape* shape) { + ShapeInfo info; + ShapeInfoUtil::collectInfoFromShape(shape, info); + return releaseShape(info); +} + +void ShapeManager::collectGarbage() { + int numShapes = _pendingGarbage.size(); + for (int i = 0; i < numShapes; ++i) { + DoubleHashKey& key = _pendingGarbage[i]; + ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef && shapeRef->_refCount == 0) { + delete shapeRef->_shape; + _shapeMap.remove(key); + } + } + _pendingGarbage.clear(); +} + +int ShapeManager::getNumReferences(const ShapeInfo& info) const { + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + const ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef) { + return shapeRef->_refCount; + } + return -1; +} + + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h new file mode 100644 index 0000000000..6eb4f363ff --- /dev/null +++ b/libraries/physics/src/ShapeManager.h @@ -0,0 +1,56 @@ +// +// ShapeManager.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// 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_ShapeManager_h +#define hifi_ShapeManager_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +#include + +#include "DoubleHashKey.h" + +class ShapeManager { +public: + + ShapeManager(); + ~ShapeManager(); + + /// \return pointer to shape + btCollisionShape* getShape(const ShapeInfo& info); + + /// \return true if shape was found and released + bool releaseShape(const ShapeInfo& info); + bool releaseShape(const btCollisionShape* shape); + + /// delete shapes that have zero references + void collectGarbage(); + + // validation methods + int getNumShapes() const { return _shapeMap.size(); } + int getNumReferences(const ShapeInfo& info) const; + +private: + struct ShapeReference { + int _refCount; + btCollisionShape* _shape; + ShapeReference() : _refCount(0), _shape(NULL) {} + }; + + btHashMap _shapeMap; + btAlignedObjectArray _pendingGarbage; +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_ShapeManager_h diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp new file mode 100644 index 0000000000..e70bdc7210 --- /dev/null +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -0,0 +1,91 @@ +/* + * Bullet Continuous Collision Detection and Physics Library + * Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it freely, + * subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * Copied and modified from btDiscreteDynamicsWorld.cpp by AndrewMeadows on 2014.11.12. + * */ + +#include + +#include "ThreadSafeDynamicsWorld.h" + +#ifdef USE_BULLET_PHYSICS +ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( + btDispatcher* dispatcher, + btBroadphaseInterface* pairCache, + btConstraintSolver* constraintSolver, + btCollisionConfiguration* collisionConfiguration, + EntityTree* entities) + : btDiscreteDynamicsWorld(dispatcher, pairCache, constraintSolver, collisionConfiguration) { + assert(entities); + _entities = entities; +} + +void ThreadSafeDynamicsWorld::synchronizeMotionStates() { + _entities->lockForWrite(); + btDiscreteDynamicsWorld::synchronizeMotionStates(); + _entities->unlock(); +} + +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; + } + } + + /*//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); + + applyGravity(); + + for (int i=0;i + +class EntityTree; + +ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld { +public: + BT_DECLARE_ALIGNED_ALLOCATOR(); + + ThreadSafeDynamicsWorld( + btDispatcher* dispatcher, + btBroadphaseInterface* pairCache, + btConstraintSolver* constraintSolver, + btCollisionConfiguration* collisionConfiguration, + EntityTree* entities); + + // virtual overrides from btDiscreteDynamicsWorld + int stepSimulation( btScalar timeStep, int maxSubSteps=1, btScalar fixedTimeStep=btScalar(1.)/btScalar(60.)); + void synchronizeMotionStates(); + + // btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated + // but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide + // smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop). + float getLocalTimeAccumulation() const { return m_localTime; } + +private: + EntityTree* _entities; +}; + +#else // USE_BULLET_PHYSICS +// stubbery for ThreadSafeDynamicsWorld when Bullet not available +class ThreadSafeDynamicsWorld { +public: + ThreadSafeDynamicsWorld() {} +}; + +#endif // USE_BULLET_PHYSICS + +#endif // hifi_ThreadSafeDynamicsWorld_h diff --git a/libraries/physics/src/VerletCapsuleShape.cpp b/libraries/physics/src/VerletCapsuleShape.cpp index 78e3f6763b..a04c36d56d 100644 --- a/libraries/physics/src/VerletCapsuleShape.cpp +++ b/libraries/physics/src/VerletCapsuleShape.cpp @@ -1,6 +1,6 @@ // // VerletCapsuleShape.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletCapsuleShape.h b/libraries/physics/src/VerletCapsuleShape.h index 828e5def6c..19afab2b61 100644 --- a/libraries/physics/src/VerletCapsuleShape.h +++ b/libraries/physics/src/VerletCapsuleShape.h @@ -1,6 +1,6 @@ // // VerletCapsuleShape.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletPoint.cpp b/libraries/physics/src/VerletPoint.cpp index cf9aeca149..895f3be8b2 100644 --- a/libraries/physics/src/VerletPoint.cpp +++ b/libraries/physics/src/VerletPoint.cpp @@ -1,6 +1,6 @@ // // VerletPoint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletPoint.h b/libraries/physics/src/VerletPoint.h index 3c73e5eb01..d80698c9e2 100644 --- a/libraries/physics/src/VerletPoint.h +++ b/libraries/physics/src/VerletPoint.h @@ -1,6 +1,6 @@ // // VerletPoint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletSphereShape.cpp b/libraries/physics/src/VerletSphereShape.cpp index da8242f26a..83dcbd9a58 100644 --- a/libraries/physics/src/VerletSphereShape.cpp +++ b/libraries/physics/src/VerletSphereShape.cpp @@ -1,6 +1,6 @@ // // VerletSphereShape.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletSphereShape.h b/libraries/physics/src/VerletSphereShape.h index c9a23faef2..596f4a517b 100644 --- a/libraries/physics/src/VerletSphereShape.h +++ b/libraries/physics/src/VerletSphereShape.h @@ -1,6 +1,6 @@ // // VerletSphereShape.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VoxelObject.h b/libraries/physics/src/VoxelObject.h new file mode 100644 index 0000000000..1372f761c7 --- /dev/null +++ b/libraries/physics/src/VoxelObject.h @@ -0,0 +1,31 @@ +// +// VoxelObject.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// 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_VoxelObject_h +#define hifi_VoxelObject_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +// VoxelObject is a simple wrapper for tracking a Voxel in a PhysicsEngine +class VoxelObject { +public: + VoxelObject(const glm::vec3& center, btCollisionObject* object) : _object(object), _center(center) { + assert(object != NULL); + } + btCollisionObject* _object; + glm::vec3 _center; +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_VoxelObject_h diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 0dd6f9d0b8..fc84489a2f 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -25,24 +25,37 @@ #include "RenderUtil.h" #include "TextureCache.h" +#include "simple_vert.h" +#include "simple_frag.h" + +#include "deferred_light_vert.h" +#include "deferred_light_limited_vert.h" + +#include "directional_light_frag.h" +#include "directional_light_shadow_map_frag.h" +#include "directional_light_cascaded_shadow_map_frag.h" + +#include "point_light_frag.h" +#include "spot_light_frag.h" + void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { _viewState = viewState; - _simpleProgram.addShaderFromSourceFile(QGLShader::Vertex, PathUtils::resourcesPath() + "shaders/simple.vert"); - _simpleProgram.addShaderFromSourceFile(QGLShader::Fragment, PathUtils::resourcesPath() + "shaders/simple.frag"); + _simpleProgram.addShaderFromSourceCode(QGLShader::Vertex, simple_vert); + _simpleProgram.addShaderFromSourceCode(QGLShader::Fragment, simple_frag); _simpleProgram.link(); _simpleProgram.bind(); _glowIntensityLocation = _simpleProgram.uniformLocation("glowIntensity"); _simpleProgram.release(); - loadLightProgram("shaders/directional_light.frag", false, _directionalLight, _directionalLightLocations); - loadLightProgram("shaders/directional_light_shadow_map.frag", false, _directionalLightShadowMap, + loadLightProgram(directional_light_frag, false, _directionalLight, _directionalLightLocations); + loadLightProgram(directional_light_shadow_map_frag, false, _directionalLightShadowMap, _directionalLightShadowMapLocations); - loadLightProgram("shaders/directional_light_cascaded_shadow_map.frag", false, _directionalLightCascadedShadowMap, + loadLightProgram(directional_light_cascaded_shadow_map_frag, false, _directionalLightCascadedShadowMap, _directionalLightCascadedShadowMapLocations); - loadLightProgram("shaders/point_light.frag", true, _pointLight, _pointLightLocations); - loadLightProgram("shaders/spot_light.frag", true, _spotLight, _spotLightLocations); + loadLightProgram(point_light_frag, true, _pointLight, _pointLightLocations); + loadLightProgram(spot_light_frag, true, _spotLight, _spotLightLocations); } void DeferredLightingEffect::bindSimpleProgram() { @@ -400,10 +413,9 @@ void DeferredLightingEffect::render() { _postLightingRenderables.clear(); } -void DeferredLightingEffect::loadLightProgram(const char* name, bool limited, ProgramObject& program, LightLocations& locations) { - program.addShaderFromSourceFile(QGLShader::Vertex, PathUtils::resourcesPath() + - (limited ? "shaders/deferred_light_limited.vert" : "shaders/deferred_light.vert")); - program.addShaderFromSourceFile(QGLShader::Fragment, PathUtils::resourcesPath() + name); +void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limited, ProgramObject& program, LightLocations& locations) { + program.addShaderFromSourceCode(QGLShader::Vertex, (limited ? deferred_light_limited_vert : deferred_light_vert)); + program.addShaderFromSourceCode(QGLShader::Fragment, fragSource); program.link(); program.bind(); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 4f3b930a09..bf8bb53c92 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -86,7 +86,7 @@ private: int radius; }; - static void loadLightProgram(const char* name, bool limited, ProgramObject& program, LightLocations& locations); + static void loadLightProgram(const char* fragSource, bool limited, ProgramObject& program, LightLocations& locations); ProgramObject _simpleProgram; int _glowIntensityLocation; diff --git a/interface/resources/shaders/deferred_light.vert b/libraries/render-utils/src/deferred_light.slv similarity index 82% rename from interface/resources/shaders/deferred_light.vert rename to libraries/render-utils/src/deferred_light.slv index 88076165a7..f26539fe69 100644 --- a/interface/resources/shaders/deferred_light.vert +++ b/libraries/render-utils/src/deferred_light.slv @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // deferred_light.vert // vertex shader diff --git a/interface/resources/shaders/deferred_light_limited.vert b/libraries/render-utils/src/deferred_light_limited.slv similarity index 87% rename from interface/resources/shaders/deferred_light_limited.vert rename to libraries/render-utils/src/deferred_light_limited.slv index f360f0307d..fdfb88d806 100644 --- a/interface/resources/shaders/deferred_light_limited.vert +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // deferred_light_limited.vert // vertex shader diff --git a/interface/resources/shaders/directional_light.frag b/libraries/render-utils/src/directional_light.slf similarity index 96% rename from interface/resources/shaders/directional_light.frag rename to libraries/render-utils/src/directional_light.slf index 906a33ea74..70020320c5 100644 --- a/interface/resources/shaders/directional_light.frag +++ b/libraries/render-utils/src/directional_light.slf @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // directional_light.frag // fragment shader diff --git a/interface/resources/shaders/directional_light_cascaded_shadow_map.frag b/libraries/render-utils/src/directional_light_cascaded_shadow_map.slf similarity index 97% rename from interface/resources/shaders/directional_light_cascaded_shadow_map.frag rename to libraries/render-utils/src/directional_light_cascaded_shadow_map.slf index ac9c784d9a..c47ac7121d 100644 --- a/interface/resources/shaders/directional_light_cascaded_shadow_map.frag +++ b/libraries/render-utils/src/directional_light_cascaded_shadow_map.slf @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // directional_light.frag // fragment shader diff --git a/interface/resources/shaders/directional_light_shadow_map.frag b/libraries/render-utils/src/directional_light_shadow_map.slf similarity index 97% rename from interface/resources/shaders/directional_light_shadow_map.frag rename to libraries/render-utils/src/directional_light_shadow_map.slf index f1eaa20826..fd3d5f161f 100644 --- a/interface/resources/shaders/directional_light_shadow_map.frag +++ b/libraries/render-utils/src/directional_light_shadow_map.slf @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // directional_light.frag // fragment shader diff --git a/interface/resources/shaders/point_light.frag b/libraries/render-utils/src/point_light.slf similarity index 97% rename from interface/resources/shaders/point_light.frag rename to libraries/render-utils/src/point_light.slf index 1d1b4f4073..320dc93f5e 100644 --- a/interface/resources/shaders/point_light.frag +++ b/libraries/render-utils/src/point_light.slf @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // spot_light.frag // fragment shader diff --git a/interface/resources/shaders/simple.frag b/libraries/render-utils/src/simple.slf similarity index 89% rename from interface/resources/shaders/simple.frag rename to libraries/render-utils/src/simple.slf index 347cb0a859..50e584669d 100644 --- a/interface/resources/shaders/simple.frag +++ b/libraries/render-utils/src/simple.slf @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // simple.frag // fragment shader diff --git a/interface/resources/shaders/simple.vert b/libraries/render-utils/src/simple.slv similarity index 88% rename from interface/resources/shaders/simple.vert rename to libraries/render-utils/src/simple.slv index c8fa0a9ebb..e874a6d8a9 100644 --- a/interface/resources/shaders/simple.vert +++ b/libraries/render-utils/src/simple.slv @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // simple.vert // vertex shader diff --git a/interface/resources/shaders/spot_light.frag b/libraries/render-utils/src/spot_light.slf similarity index 97% rename from interface/resources/shaders/spot_light.frag rename to libraries/render-utils/src/spot_light.slf index 590ed8b6ac..489802d061 100644 --- a/interface/resources/shaders/spot_light.frag +++ b/libraries/render-utils/src/spot_light.slf @@ -1,5 +1,6 @@ -#version 120 - +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> // // spot_light.frag // fragment shader diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index 7067175f3a..b29bc49bef 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -78,6 +78,10 @@ inline bool operator==(const AACube& a, const AACube& b) { return a.getCorner() == b.getCorner() && a.getScale() == b.getScale(); } +inline bool operator!=(const AACube& a, const AACube& b) { + return a.getCorner() != b.getCorner() || a.getScale() != b.getScale(); +} + inline QDebug operator<<(QDebug debug, const AACube& cube) { debug << "AACube[ (" << cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to (" diff --git a/libraries/physics/src/AACubeShape.cpp b/libraries/shared/src/AACubeShape.cpp similarity index 98% rename from libraries/physics/src/AACubeShape.cpp rename to libraries/shared/src/AACubeShape.cpp index fa1a45b809..30197d6bfd 100644 --- a/libraries/physics/src/AACubeShape.cpp +++ b/libraries/shared/src/AACubeShape.cpp @@ -13,7 +13,7 @@ #include #include "AACubeShape.h" -#include // for SQUARE_ROOT_OF_3 +#include "SharedUtil.h" // for SQUARE_ROOT_OF_3 glm::vec3 faceNormals[3] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f) }; diff --git a/libraries/physics/src/AACubeShape.h b/libraries/shared/src/AACubeShape.h similarity index 100% rename from libraries/physics/src/AACubeShape.h rename to libraries/shared/src/AACubeShape.h diff --git a/libraries/physics/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp similarity index 99% rename from libraries/physics/src/CapsuleShape.cpp rename to libraries/shared/src/CapsuleShape.cpp index 778798c15b..24ac6634ea 100644 --- a/libraries/physics/src/CapsuleShape.cpp +++ b/libraries/shared/src/CapsuleShape.cpp @@ -12,11 +12,9 @@ #include #include -#include -#include - #include "CapsuleShape.h" - +#include "GeometryUtil.h" +#include "SharedUtil.h" CapsuleShape::CapsuleShape() : Shape(CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {} diff --git a/libraries/physics/src/CapsuleShape.h b/libraries/shared/src/CapsuleShape.h similarity index 98% rename from libraries/physics/src/CapsuleShape.h rename to libraries/shared/src/CapsuleShape.h index ede6993b40..ad3066bab7 100644 --- a/libraries/physics/src/CapsuleShape.h +++ b/libraries/shared/src/CapsuleShape.h @@ -12,9 +12,8 @@ #ifndef hifi_CapsuleShape_h #define hifi_CapsuleShape_h -#include - #include "Shape.h" +#include "SharedUtil.h" // default axis of CapsuleShape is Y-axis const glm::vec3 DEFAULT_CAPSULE_AXIS(0.0f, 1.0f, 0.0f); diff --git a/libraries/physics/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp similarity index 99% rename from libraries/physics/src/CollisionInfo.cpp rename to libraries/shared/src/CollisionInfo.cpp index bfa1606e63..1403e444cf 100644 --- a/libraries/physics/src/CollisionInfo.cpp +++ b/libraries/shared/src/CollisionInfo.cpp @@ -10,10 +10,10 @@ // -#include #include "CollisionInfo.h" #include "Shape.h" +#include "SharedUtil.h" CollisionInfo::CollisionInfo() : _data(NULL), diff --git a/libraries/physics/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h similarity index 100% rename from libraries/physics/src/CollisionInfo.h rename to libraries/shared/src/CollisionInfo.h diff --git a/libraries/physics/src/ListShape.cpp b/libraries/shared/src/ListShape.cpp similarity index 100% rename from libraries/physics/src/ListShape.cpp rename to libraries/shared/src/ListShape.cpp diff --git a/libraries/physics/src/ListShape.h b/libraries/shared/src/ListShape.h similarity index 100% rename from libraries/physics/src/ListShape.h rename to libraries/shared/src/ListShape.h diff --git a/libraries/physics/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp similarity index 98% rename from libraries/physics/src/PlaneShape.cpp rename to libraries/shared/src/PlaneShape.cpp index 1a4122f3aa..a247641122 100644 --- a/libraries/physics/src/PlaneShape.cpp +++ b/libraries/shared/src/PlaneShape.cpp @@ -9,10 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include - +#include "GLMHelpers.h" #include "PlaneShape.h" +#include "SharedUtil.h" const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f); diff --git a/libraries/physics/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h similarity index 100% rename from libraries/physics/src/PlaneShape.h rename to libraries/shared/src/PlaneShape.h diff --git a/libraries/physics/src/RayIntersectionInfo.h b/libraries/shared/src/RayIntersectionInfo.h similarity index 97% rename from libraries/physics/src/RayIntersectionInfo.h rename to libraries/shared/src/RayIntersectionInfo.h index 6c4eb3f8dd..06ec0aceaa 100644 --- a/libraries/physics/src/RayIntersectionInfo.h +++ b/libraries/shared/src/RayIntersectionInfo.h @@ -1,6 +1,6 @@ // // RayIntersectionInfo.h -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.09.09 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/Shape.h b/libraries/shared/src/Shape.h similarity index 93% rename from libraries/physics/src/Shape.h rename to libraries/shared/src/Shape.h index e529761dfc..dd318c2776 100644 --- a/libraries/physics/src/Shape.h +++ b/libraries/shared/src/Shape.h @@ -25,12 +25,19 @@ class VerletPoint; const float MAX_SHAPE_MASS = 1.0e18f; // something less than sqrt(FLT_MAX) +// DANGER: until ShapeCollider goes away the order of these values matter. Specifically, +// UNKNOWN_SHAPE must be equal to the number of shapes that ShapeCollider actually supports. const quint8 SPHERE_SHAPE = 0; const quint8 CAPSULE_SHAPE = 1; const quint8 PLANE_SHAPE = 2; const quint8 AACUBE_SHAPE = 3; const quint8 LIST_SHAPE = 4; const quint8 UNKNOWN_SHAPE = 5; +const quint8 INVALID_SHAPE = 5; + +// new shapes to be supported by Bullet +const quint8 BOX_SHAPE = 7; +const quint8 CYLINDER_SHAPE = 8; class Shape { public: diff --git a/libraries/physics/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp similarity index 99% rename from libraries/physics/src/ShapeCollider.cpp rename to libraries/shared/src/ShapeCollider.cpp index a147487f33..b4f9d984f7 100644 --- a/libraries/physics/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -11,16 +11,14 @@ #include -#include -#include - -#include "ShapeCollider.h" - #include "AACubeShape.h" #include "CapsuleShape.h" +#include "GeometryUtil.h" #include "ListShape.h" #include "PlaneShape.h" +#include "ShapeCollider.h" #include "SphereShape.h" +#include "StreamUtils.h" // NOTE: diff --git a/libraries/physics/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h similarity index 99% rename from libraries/physics/src/ShapeCollider.h rename to libraries/shared/src/ShapeCollider.h index 7414665ca7..da37adcd49 100644 --- a/libraries/physics/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -14,10 +14,10 @@ #include -#include - #include "CollisionInfo.h" #include "RayIntersectionInfo.h" +#include "SharedUtil.h" + class Shape; class SphereShape; diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp new file mode 100644 index 0000000000..d402a048a0 --- /dev/null +++ b/libraries/shared/src/ShapeInfo.cpp @@ -0,0 +1,60 @@ +// +// ShapeInfo.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// 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 + +#include "SharedUtil.h" // for MILLIMETERS_PER_METER + +//#include "DoubleHashKey.h" +#include "ShapeInfo.h" + +void ShapeInfo::clear() { + _type = INVALID_SHAPE; + _data.clear(); +} + +void ShapeInfo::setBox(const glm::vec3& halfExtents) { + _type = BOX_SHAPE; + _data.clear(); + _data.push_back(halfExtents); +} + +void ShapeInfo::setSphere(float radius) { + _type = SPHERE_SHAPE; + _data.clear(); + _data.push_back(glm::vec3(radius)); +} + +void ShapeInfo::setCylinder(float radius, float halfHeight) { + _type = CYLINDER_SHAPE; + _data.clear(); + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + _data.push_back(glm::vec3(radius, halfHeight, radius)); +} + +void ShapeInfo::setCapsule(float radius, float halfHeight) { + _type = CAPSULE_SHAPE; + _data.clear(); + _data.push_back(glm::vec3(radius, halfHeight, radius)); +} + +glm::vec3 ShapeInfo::getBoundingBoxDiagonal() const { + switch(_type) { + case BOX_SHAPE: + case SPHERE_SHAPE: + case CYLINDER_SHAPE: + case CAPSULE_SHAPE: + return 2.0f * _data[0]; + default: + break; + } + return glm::vec3(0.0f); +} diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h new file mode 100644 index 0000000000..7ba290122c --- /dev/null +++ b/libraries/shared/src/ShapeInfo.h @@ -0,0 +1,42 @@ +// +// ShapeInfo.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// 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_ShapeInfo_h +#define hifi_ShapeInfo_h + +#include +#include + +#include "Shape.h" + +class ShapeInfo { +public: + ShapeInfo() : _type(INVALID_SHAPE) {} + + void clear(); + + void setBox(const glm::vec3& halfExtents); + void setSphere(float radius); + void setCylinder(float radius, float halfHeight); + void setCapsule(float radius, float halfHeight); + + const int getType() const { return _type; } + const QVector& getData() const { return _data; } + + glm::vec3 getBoundingBoxDiagonal() const; + +protected: + int _type; + QVector _data; +}; + +#endif // hifi_ShapeInfo_h + diff --git a/libraries/physics/src/SphereShape.cpp b/libraries/shared/src/SphereShape.cpp similarity index 100% rename from libraries/physics/src/SphereShape.cpp rename to libraries/shared/src/SphereShape.cpp diff --git a/libraries/physics/src/SphereShape.h b/libraries/shared/src/SphereShape.h similarity index 98% rename from libraries/physics/src/SphereShape.h rename to libraries/shared/src/SphereShape.h index 72f46c1168..3bce2e96a1 100644 --- a/libraries/physics/src/SphereShape.h +++ b/libraries/shared/src/SphereShape.h @@ -12,9 +12,8 @@ #ifndef hifi_SphereShape_h #define hifi_SphereShape_h -#include - #include "Shape.h" +#include "SharedUtil.h" class SphereShape : public Shape { diff --git a/tests/physics/CMakeLists.txt b/tests/physics/CMakeLists.txt index 31c741eecf..14e6c56df3 100644 --- a/tests/physics/CMakeLists.txt +++ b/tests/physics/CMakeLists.txt @@ -3,8 +3,8 @@ set(TARGET_NAME physics-tests) setup_hifi_project() include_glm() +include_bullet() -# link in the shared libraries link_hifi_libraries(shared physics) include_dependency_includes() diff --git a/tests/physics/src/BulletUtilTests.cpp b/tests/physics/src/BulletUtilTests.cpp new file mode 100644 index 0000000000..06b1815c6e --- /dev/null +++ b/tests/physics/src/BulletUtilTests.cpp @@ -0,0 +1,114 @@ +// +// BulletUtilTests.cpp +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// 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 + +#include +#include + +#include "BulletUtilTests.h" + +#ifdef USE_BULLET_PHYSICS +void BulletUtilTests::fromBulletToGLM() { + btVector3 bV(1.23f, 4.56f, 7.89f); + glm::vec3 gV = bulletToGLM(bV); + if (gV.x != bV.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.x = " << bV.getX() << " != glm.x = " << gV.x << std::endl; + } + if (gV.y != bV.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.y = " << bV.getY() << " != glm.y = " << gV.y << std::endl; + } + if (gV.z != bV.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.z = " << bV.getZ() << " != glm.z = " << gV.z << std::endl; + } + + float angle = 0.317f * PI; + btVector3 axis(1.23f, 2.34f, 3.45f); + axis.normalize(); + btQuaternion bQ(axis, angle); + + glm::quat gQ = bulletToGLM(bQ); + if (gQ.x != bQ.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.x = " << bQ.getX() << " != glm.x = " << gQ.x << std::endl; + } + if (gQ.y != bQ.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.y = " << bQ.getY() << " != glm.y = " << gQ.y << std::endl; + } + if (gQ.z != bQ.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.z = " << bQ.getZ() << " != glm.z = " << gQ.z << std::endl; + } + if (gQ.w != bQ.getW()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.w = " << bQ.getW() << " != glm.w = " << gQ.w << std::endl; + } +} + +void BulletUtilTests::fromGLMToBullet() { + glm::vec3 gV(1.23f, 4.56f, 7.89f); + btVector3 bV = glmToBullet(gV); + if (gV.x != bV.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.x = " << gV.x << " != bullet.x = " << bV.getX() << std::endl; + } + if (gV.y != bV.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.y = " << gV.y << " != bullet.y = " << bV.getY() << std::endl; + } + if (gV.z != bV.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.z = " << gV.z << " != bullet.z = " << bV.getZ() << std::endl; + } + + float angle = 0.317f * PI; + btVector3 axis(1.23f, 2.34f, 3.45f); + axis.normalize(); + btQuaternion bQ(axis, angle); + + glm::quat gQ = bulletToGLM(bQ); + if (gQ.x != bQ.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.x = " << gQ.x << " != bullet.x = " << bQ.getX() << std::endl; + } + if (gQ.y != bQ.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.y = " << gQ.y << " != bullet.y = " << bQ.getY() << std::endl; + } + if (gQ.z != bQ.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.z = " << gQ.z << " != bullet.z = " << bQ.getZ() << std::endl; + } + if (gQ.w != bQ.getW()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.w = " << gQ.w << " != bullet.w = " << bQ.getW() << std::endl; + } +} + +void BulletUtilTests::runAllTests() { + fromBulletToGLM(); + fromGLMToBullet(); +} + +#else // USE_BULLET_PHYSICS +void BulletUtilTests::fromBulletToGLM() { +} + +void BulletUtilTests::fromGLMToBullet() { +} + +void BulletUtilTests::runAllTests() { +} +#endif // USE_BULLET_PHYSICS diff --git a/tests/physics/src/BulletUtilTests.h b/tests/physics/src/BulletUtilTests.h new file mode 100644 index 0000000000..b885777bdd --- /dev/null +++ b/tests/physics/src/BulletUtilTests.h @@ -0,0 +1,21 @@ +// +// BulletUtilTests.h +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// 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_BulletUtilTests_h +#define hifi_BulletUtilTests_h + +namespace BulletUtilTests { + void fromBulletToGLM(); + void fromGLMToBullet(); + void runAllTests(); +} + +#endif // hifi_BulletUtilTests_h diff --git a/tests/physics/src/ShapeInfoTests.cpp b/tests/physics/src/ShapeInfoTests.cpp new file mode 100644 index 0000000000..a4db3a5764 --- /dev/null +++ b/tests/physics/src/ShapeInfoTests.cpp @@ -0,0 +1,260 @@ +// +// ShapeInfoTests.cpp +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// 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 + +#ifdef USE_BULLET_PHYSICS +#include +#include +#endif // USE_BULLET_PHYSICS + +#include +#include +#include +#include + +#include "ShapeInfoTests.h" + +void ShapeInfoTests::testHashFunctions() { +#ifdef USE_BULLET_PHYSICS + int maxTests = 10000000; + ShapeInfo info; + btHashMap hashes; + + int bits[32]; + unsigned int masks[32]; + for (int i = 0; i < 32; ++i) { + bits[i] = 0; + masks[i] = 1U << i; + } + + float deltaLength = 0.002f; + float endLength = 100.0f; + int numSteps = (int)(endLength / deltaLength); + + int testCount = 0; + int numCollisions = 0; + btClock timer; + for (int x = 1; x < numSteps && testCount < maxTests; ++x) { + float radiusX = (float)x * deltaLength; + // test sphere + info.setSphere(radiusX); + ++testCount; + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + int* hashPtr = hashes.find(key._hash); + if (hashPtr && *hashPtr == key._hash2) { + std::cout << testCount << " hash collision radiusX = " << radiusX + << " h1 = 0x" << std::hex << (unsigned int)(key._hash) + << " h2 = 0x" << std::hex << (unsigned int)(key._hash2) + << std::endl; + ++numCollisions; + assert(false); + } else { + hashes.insert(key._hash, key._hash2); + } + for (int k = 0; k < 32; ++k) { + if (masks[k] & key._hash2) { + ++bits[k]; + } + } + + for (int y = 1; y < numSteps && testCount < maxTests; ++y) { + float radiusY = (float)y * deltaLength; + // test cylinder and capsule + int types[] = { CYLINDER_SHAPE_PROXYTYPE, CAPSULE_SHAPE_PROXYTYPE }; + for (int i = 0; i < 2; ++i) { + switch(types[i]) { + case CYLINDER_SHAPE_PROXYTYPE: { + info.setCylinder(radiusX, radiusY); + break; + } + case CAPSULE_SHAPE_PROXYTYPE: { + info.setCapsule(radiusX, radiusY); + break; + } + } + + ++testCount; + key = ShapeInfoUtil::computeHash(info); + hashPtr = hashes.find(key._hash); + if (hashPtr && *hashPtr == key._hash2) { + std::cout << testCount << " hash collision radiusX = " << radiusX << " radiusY = " << radiusY + << " h1 = 0x" << std::hex << (unsigned int)(key._hash) + << " h2 = 0x" << std::hex << (unsigned int)(key._hash2) + << std::endl; + ++numCollisions; + assert(false); + } else { + hashes.insert(key._hash, key._hash2); + } + for (int k = 0; k < 32; ++k) { + if (masks[k] & key._hash2) { + ++bits[k]; + } + } + } + + for (int z = 1; z < numSteps && testCount < maxTests; ++z) { + float radiusZ = (float)z * deltaLength; + // test box + info.setBox(glm::vec3(radiusX, radiusY, radiusZ)); + ++testCount; + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + hashPtr = hashes.find(key._hash); + if (hashPtr && *hashPtr == key._hash2) { + std::cout << testCount << " hash collision radiusX = " << radiusX + << " radiusY = " << radiusY << " radiusZ = " << radiusZ + << " h1 = 0x" << std::hex << (unsigned int)(key._hash) + << " h2 = 0x" << std::hex << (unsigned int)(key._hash2) + << std::endl; + ++numCollisions; + assert(false); + } else { + hashes.insert(key._hash, key._hash2); + } + for (int k = 0; k < 32; ++k) { + if (masks[k] & key._hash2) { + ++bits[k]; + } + } + } + } + } + unsigned long int msec = timer.getTimeMilliseconds(); + std::cout << msec << " msec with " << numCollisions << " collisions out of " << testCount << " hashes" << std::endl; + + // print out distribution of bits + for (int i = 0; i < 32; ++i) { + std::cout << "bit 0x" << std::hex << masks[i] << std::dec << " = " << bits[i] << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testBoxShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + glm::vec3 halfExtents(1.23f, 4.56f, 7.89f); + info.setBox(halfExtents); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: NULL Box shape" << std::endl; + } + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Box shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Box shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testSphereShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + info.setSphere(radius); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Sphere shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Sphere shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testCylinderShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCylinder(radius, height); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Cylinder shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Cylinder shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testCapsuleShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCapsule(radius, height); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Capsule shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Capsule shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::runAllTests() { +//#define MANUAL_TEST +#ifdef MANUAL_TEST + testHashFunctions(); +#endif // MANUAL_TEST + testBoxShape(); + testSphereShape(); + testCylinderShape(); + testCapsuleShape(); +} diff --git a/tests/physics/src/ShapeInfoTests.h b/tests/physics/src/ShapeInfoTests.h new file mode 100644 index 0000000000..b786ca92d5 --- /dev/null +++ b/tests/physics/src/ShapeInfoTests.h @@ -0,0 +1,24 @@ +// +// ShapeInfoTests.h +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// 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_ShapeInfoTests_h +#define hifi_ShapeInfoTests_h + +namespace ShapeInfoTests { + void testHashFunctions(); + void testBoxShape(); + void testSphereShape(); + void testCylinderShape(); + void testCapsuleShape(); + void runAllTests(); +} + +#endif // hifi_ShapeInfoTests_h diff --git a/tests/physics/src/ShapeManagerTests.cpp b/tests/physics/src/ShapeManagerTests.cpp new file mode 100644 index 0000000000..5be247f25f --- /dev/null +++ b/tests/physics/src/ShapeManagerTests.cpp @@ -0,0 +1,247 @@ +// +// ShapeManagerTests.cpp +// tests/physics/src +// +// Created by Andrew Meadows on 2014.10.30 +// 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 +#include +#include +#include + +#include "ShapeManagerTests.h" + +void ShapeManagerTests::testShapeAccounting() { +#ifdef USE_BULLET_PHYSICS + ShapeManager shapeManager; + ShapeInfo info; + info.setBox(glm::vec3(1.0f, 1.0f, 1.0f)); + + // NOTE: ShapeManager returns -1 as refcount when the shape is unknown, + // which is distinct from "known but with zero references" + int numReferences = shapeManager.getNumReferences(info); + if (numReferences != -1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected ignorant ShapeManager after initialization" << std::endl; + } + + // create one shape and verify we get a valid pointer + btCollisionShape* shape = shapeManager.getShape(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected shape creation for default parameters" << std::endl; + } + + // verify number of shapes + if (shapeManager.getNumShapes() != 1) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape" << std::endl; + } + + // reference the shape again and verify that we get the same pointer + btCollisionShape* otherShape = shapeManager.getShape(info); + if (otherShape != shape) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected shape* " << (void*)(shape) + << " but found shape* " << (void*)(otherShape) << std::endl; + } + + // verify number of references + numReferences = shapeManager.getNumReferences(info); + int expectedNumReferences = 2; + if (numReferences != expectedNumReferences) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected " << expectedNumReferences + << " references but found " << numReferences << std::endl; + } + + // release all references + bool released = shapeManager.releaseShape(info); + numReferences--; + while (numReferences > 0) { + released = shapeManager.releaseShape(info) && released; + numReferences--; + } + if (!released) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected shape released" << std::endl; + } + + // verify shape still exists (not yet garbage collected) + if (shapeManager.getNumShapes() != 1) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape after release but before garbage collection" << std::endl; + } + + // verify shape's refcount is zero + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 0) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 0 for shape but found refcount = " << numReferences << std::endl; + } + + // reference the shape again and verify refcount is updated + otherShape = shapeManager.getShape(info); + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl; + } + + // verify that shape is not collected as garbage + shapeManager.collectGarbage(); + if (shapeManager.getNumShapes() != 1) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape after release" << std::endl; + } + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl; + } + + // release reference and verify that it is collected as garbage + released = shapeManager.releaseShape(info); + shapeManager.collectGarbage(); + if (shapeManager.getNumShapes() != 0) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected zero shapes after release" << std::endl; + } + numReferences = shapeManager.getNumReferences(info); + if (numReferences != -1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected ignorant ShapeManager after garbage collection" << std::endl; + } + + // add the shape again and verify that it gets added again + otherShape = shapeManager.getShape(info); + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addManyShapes() { +#ifdef USE_BULLET_PHYSICS + ShapeManager shapeManager; + + int numSizes = 100; + float startSize = 1.0f; + float endSize = 99.0f; + float deltaSize = (endSize - startSize) / (float)numSizes; + ShapeInfo info; + for (int i = 0; i < numSizes; ++i) { + float s = startSize + (float)i * deltaSize; + glm::vec3 scale(s, 1.23f + s, s - 0.573f); + info.setBox(0.5f * scale); + btCollisionShape* shape = shapeManager.getShape(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: i = " << i << " null box shape for scale = " << scale << std::endl; + } + float radius = 0.5f * s; + info.setSphere(radius); + shape = shapeManager.getShape(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: i = " << i << " null sphere shape for radius = " << radius << std::endl; + } + } + int numShapes = shapeManager.getNumShapes(); + if (numShapes != 2 * numSizes) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected numShapes = " << numSizes << " but found numShapes = " << numShapes << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addBoxShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + glm::vec3 halfExtents(1.23f, 4.56f, 7.89f); + info.setBox(halfExtents); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Box ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addSphereShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + info.setSphere(radius); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Sphere ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addCylinderShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCylinder(radius, height); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Cylinder ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addCapsuleShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCapsule(radius, height); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Capsule ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::runAllTests() { + testShapeAccounting(); + addManyShapes(); + addBoxShape(); + addSphereShape(); + addCylinderShape(); + addCapsuleShape(); +} diff --git a/tests/physics/src/ShapeManagerTests.h b/tests/physics/src/ShapeManagerTests.h new file mode 100644 index 0000000000..98703fda1e --- /dev/null +++ b/tests/physics/src/ShapeManagerTests.h @@ -0,0 +1,25 @@ +// +// ShapeManagerTests.h +// tests/physics/src +// +// Created by Andrew Meadows on 2014.10.30 +// 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_ShapeManagerTests_h +#define hifi_ShapeManagerTests_h + +namespace ShapeManagerTests { + void testShapeAccounting(); + void addManyShapes(); + void addBoxShape(); + void addSphereShape(); + void addCylinderShape(); + void addCapsuleShape(); + void runAllTests(); +} + +#endif // hifi_ShapeManagerTests_h diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp index 086bff4dcd..bcf26f4115 100644 --- a/tests/physics/src/main.cpp +++ b/tests/physics/src/main.cpp @@ -10,9 +10,15 @@ #include "ShapeColliderTests.h" #include "VerletShapeTests.h" +#include "ShapeInfoTests.h" +#include "ShapeManagerTests.h" +#include "BulletUtilTests.h" int main(int argc, char** argv) { ShapeColliderTests::runAllTests(); VerletShapeTests::runAllTests(); + ShapeInfoTests::runAllTests(); + ShapeManagerTests::runAllTests(); + BulletUtilTests::runAllTests(); return 0; } diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index d36c82a85b..f68272c0ab 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -50,7 +50,7 @@ int main (int argc, char** argv) { case READY: { if (inputs.back() == "-o") { mode = GRAB_OUTPUT; - } else if (inputs.back() == "-tn") { + } else if (inputs.back() == "-t") { mode = GRAB_TARGET_NAME; } else if (inputs.back() == "-D") { mode = GRAB_VAR_NAME;