diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 169077ed78..e905a83ace 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -75,21 +75,6 @@ To prevent these problems, install OpenSSL yourself. Download the following bina Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version. -###vhacd -Download it directly from https://github.com/virneo/v-hacd - -To build it run the following commands - 1. cd src\ - 2. mkdir build - 3. cd build - 4. cmake .. - -Build using visual studio 2013. Build ALL_BUILD and INSTALL targets both in Release and Debug. - -This will create an output folder with include and lib directory inside it. - -Either copy the contents of output folder to ENV %HIFI_LIB_DIR%/vhacd or create an environment variable VHACD_ROOT_DIR to this output directory. - ###Build High Fidelity using Visual Studio Follow the same build steps from the CMake section of [BUILD.md](BUILD.md), but pass a different generator to CMake. diff --git a/CMakeLists.txt b/CMakeLists.txt index 1def1867f2..7f05548ec4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,9 +178,13 @@ option(GET_GVERB "Get Gverb library automatically as external project" 1) option(GET_SOXR "Get Soxr library automatically as external project" 1) option(GET_TBB "Get Threading Building Blocks library automatically as external project" 1) option(GET_LIBOVR "Get LibOVR library automatically as external project" 1) -option(USE_NSIGHT "Attempt to find the nSight libraries" 1) option(GET_VHACD "Get V-HACD library automatically as external project" 1) option(GET_POLYVOX "Get polyvox library automatically as external project" 1) +option(GET_OPENVR "Get OpenVR library automatically as external project" 1) +option(GET_BOOSTCONFIG "Get Boost-config library automatically as external project" 1) +option(GET_OGLPLUS "Get OGLplus library automatically as external project" 1) + +option(USE_NSIGHT "Attempt to find the nSight libraries" 1) if (WIN32) option(GET_GLEW "Get GLEW library automatically as external project" 1) diff --git a/cmake/externals/boostconfig/CMakeLists.txt b/cmake/externals/boostconfig/CMakeLists.txt new file mode 100644 index 0000000000..8785e0d7c7 --- /dev/null +++ b/cmake/externals/boostconfig/CMakeLists.txt @@ -0,0 +1,18 @@ +set(EXTERNAL_NAME boostconfig) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://github.com/boostorg/config/archive/boost-1.58.0.zip + URL_MD5 42fa673bae2b7645a22736445e80eb8d + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) + diff --git a/cmake/externals/oglplus/CMakeLists.txt b/cmake/externals/oglplus/CMakeLists.txt new file mode 100644 index 0000000000..1413edce34 --- /dev/null +++ b/cmake/externals/oglplus/CMakeLists.txt @@ -0,0 +1,18 @@ +set(EXTERNAL_NAME oglplus) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://softlayer-dal.dl.sourceforge.net/project/oglplus/oglplus-0.61.x/oglplus-0.61.0.zip + URL_MD5 bb55038c36c660d2b6c7be380414fa60 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include ${SOURCE_DIR}/implement CACHE TYPE INTERNAL) + diff --git a/cmake/externals/openvr/CMakeLists.txt b/cmake/externals/openvr/CMakeLists.txt index b5cd477c02..826c3dbb13 100644 --- a/cmake/externals/openvr/CMakeLists.txt +++ b/cmake/externals/openvr/CMakeLists.txt @@ -7,8 +7,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) ExternalProject_Add( ${EXTERNAL_NAME} - URL https://github.com/ValveSoftware/openvr/archive/0.9.0.zip - URL_MD5 4fbde7759f604aaa68b9c40d628cc34a + URL https://github.com/ValveSoftware/openvr/archive/0.9.1.zip + URL_MD5 f986f5a6815e9454c53c5bf58ce02fdc CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/modules/FindBoostConfig.cmake b/cmake/modules/FindBoostConfig.cmake new file mode 100644 index 0000000000..90682d2425 --- /dev/null +++ b/cmake/modules/FindBoostConfig.cmake @@ -0,0 +1,24 @@ +# +# Try to find BOOSTCONFIG include path. +# Once done this will define +# +# BOOSTCONFIG_INCLUDE_DIRS +# +# Created by Bradley Austin Davis on 2015/05/22 +# Copyright 2015 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 +# + +# setup hints for BOOSTCONFIG search +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("BOOSTCONFIG") + +# locate header +find_path(BOOSTCONFIG_INCLUDE_DIRS "boost/config.hpp" HINTS ${BOOSTCONFIG_SEARCH_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(BOOSTCONFIG DEFAULT_MSG BOOSTCONFIG_INCLUDE_DIRS) + +mark_as_advanced(BOOSTCONFIG_INCLUDE_DIRS BOOSTCONFIG_SEARCH_DIRS) \ No newline at end of file diff --git a/cmake/modules/FindOGLPLUS.cmake b/cmake/modules/FindOGLPLUS.cmake new file mode 100644 index 0000000000..6ba883ee2c --- /dev/null +++ b/cmake/modules/FindOGLPLUS.cmake @@ -0,0 +1,24 @@ +# +# Try to find OGLPLUS include path. +# Once done this will define +# +# OGLPLUS_INCLUDE_DIRS +# +# Created by Bradley Austin Davis on 2015/05/22 +# Copyright 2015 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 +# + +# setup hints for OGLPLUS search +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("oglplus") + +# locate header +find_path(OGLPLUS_INCLUDE_DIRS "oglplus/fwd.hpp" HINTS ${OGLPLUS_SEARCH_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OGLPLUS DEFAULT_MSG OGLPLUS_INCLUDE_DIRS) + +mark_as_advanced(OGLPLUS_INCLUDE_DIRS OGLPLUS_SEARCH_DIRS) \ No newline at end of file diff --git a/examples/example/brownianFun.js b/examples/example/brownianFun.js new file mode 100644 index 0000000000..b7f7fe3b32 --- /dev/null +++ b/examples/example/brownianFun.js @@ -0,0 +1,247 @@ +var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var SOUND_PATH = HIFI_PUBLIC_BUCKET + "sounds/Collisions-hitsandslaps/"; +var soundURLS = ["67LCollision01.wav", "67LCollision02.wav", "airhockey_hit1.wav"]; +var FLOOR_SIZE = 10; +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(FLOOR_SIZE * 1.5, Quat.getFront(Camera.getOrientation()))); +var WALL_WIDTH = .1; +var FLOOR_HEIGHT_OFFSET = -2; +var WALL_HEIGHT = FLOOR_SIZE / 4; +var BALL_DROP_HEIGHT = center.y + WALL_HEIGHT; +var NUM_BALLS = 50; +var BALL_RADIUS = 1; +var BROWNIAN_INTERVAL_TIME = 16; +var BROWNIAN_FORCE_RANGE = 5; +var SPAWN_TIME = 50; +var spawnCount = 0; + +var brownianMotionActivated = false; +var buttonOffColor = { + red: 250, + green: 10, + blue: 10 +}; +var buttonOnColor = { + red: 10, + green: 200, + blue: 100 +}; + +var bounds = { + xMin: center.x - FLOOR_SIZE / 2, + xMax: center.x + FLOOR_SIZE / 2, + zMin: center.z - FLOOR_SIZE / 2, + zMax: center.z + FLOOR_SIZE / 2 +}; +var balls = []; + +var screenSize = Controller.getViewportDimensions(); +var BUTTON_SIZE = 32; +var PADDING = 3; + +var brownianButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png", + color: buttonOffColor, + alpha: 1 +}); + +var floor = Entities.addEntity({ + type: 'Box', + position: Vec3.sum(center, { + x: 0, + y: FLOOR_HEIGHT_OFFSET, + z: 0 + }), + dimensions: { + x: FLOOR_SIZE, + y: WALL_WIDTH, + z: FLOOR_SIZE + }, + color: { + red: 100, + green: 100, + blue: 100 + } +}); + +var rightWall = Entities.addEntity({ + type: 'Box', + position: Vec3.sum(center, { + x: FLOOR_SIZE / 2, + y: FLOOR_HEIGHT_OFFSET / 2, + z: 0 + }), + dimensions: { + x: WALL_WIDTH, + y: WALL_HEIGHT, + z: FLOOR_SIZE + }, + color: { + red: 120, + green: 100, + blue: 120 + } +}); + +var leftWall = Entities.addEntity({ + type: 'Box', + position: Vec3.sum(center, { + x: -FLOOR_SIZE / 2, + y: FLOOR_HEIGHT_OFFSET / 2, + z: 0 + }), + dimensions: { + x: WALL_WIDTH, + y: WALL_HEIGHT, + z: FLOOR_SIZE + }, + color: { + red: 120, + green: 100, + blue: 120 + } +}); + +var backWall = Entities.addEntity({ + type: 'Box', + position: Vec3.sum(center, { + x: 0, + y: FLOOR_HEIGHT_OFFSET / 2, + z: -FLOOR_SIZE / 2, + }), + dimensions: { + x: FLOOR_SIZE, + y: WALL_HEIGHT, + z: WALL_WIDTH + }, + color: { + red: 120, + green: 100, + blue: 120 + } +}); + +var frontWall = Entities.addEntity({ + type: 'Box', + position: Vec3.sum(center, { + x: 0, + y: FLOOR_HEIGHT_OFFSET / 2, + z: FLOOR_SIZE / 2, + }), + dimensions: { + x: FLOOR_SIZE, + y: WALL_HEIGHT, + z: WALL_WIDTH + }, + color: { + red: 120, + green: 100, + blue: 120 + } +}); + +var spawnInterval = Script.setInterval(spawnBalls, SPAWN_TIME); + +function spawnBalls() { + balls.push(Entities.addEntity({ + type: "Sphere", + shapeType: "sphere", + position: { + x: randFloat(bounds.xMin, bounds.xMax), + y: BALL_DROP_HEIGHT, + z: randFloat(bounds.zMin, bounds.zMax) + }, + dimensions: { + x: BALL_RADIUS, + y: BALL_RADIUS, + z: BALL_RADIUS + }, + color: { + red: randFloat(100, 150), + green: randFloat(20, 80), + blue: randFloat(10, 180) + }, + ignoreCollisions: false, + collisionsWillMove: true, + gravity: { + x: 0, + y: -9.9, + z: 0 + }, + velocity: { + x: 0, + y: -.25, + z: 0 + }, + collisionSoundURL: SOUND_PATH + soundURLS[randInt(0, soundURLS.length - 1)] + })); + spawnCount++; + if (spawnCount === NUM_BALLS) { + Script.clearInterval(spawnInterval); + } +} + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == brownianButton) { + brownianMotionActivated = !brownianMotionActivated; + if (brownianMotionActivated) { + brownianInterval = Script.setInterval(bumpBalls, BROWNIAN_INTERVAL_TIME); + Overlays.editOverlay(brownianButton, { + color: buttonOnColor + }) + } else { + Script.clearInterval(brownianInterval); + Overlays.editOverlay(brownianButton, { + color: buttonOffColor + }) + } + } +} + +function bumpBalls() { + balls.forEach(function(ball) { + var props = Entities.getEntityProperties(ball); + var newVelocity = Vec3.sum(props.velocity, { + x: (Math.random() - 0.5) * BROWNIAN_FORCE_RANGE, + y: 0, + z: (Math.random() - 0.5) * BROWNIAN_FORCE_RANGE + }); + Entities.editEntity(ball, { + velocity: newVelocity + }); + }); +} + +function cleanup() { + Entities.deleteEntity(floor); + Entities.deleteEntity(rightWall); + Entities.deleteEntity(leftWall); + Entities.deleteEntity(backWall); + Entities.deleteEntity(frontWall); + balls.forEach(function(ball) { + Entities.deleteEntity(ball); + }); + Overlays.deleteOverlay(brownianButton); +} + +function randFloat(low, high) { + return Math.floor(low + Math.random() * (high - low)); +} + + +function randInt(low, high) { + return Math.floor(randFloat(low, high)); +} + +Script.scriptEnding.connect(cleanup); + + +Script.scriptEnding.connect(cleanup); +Controller.mousePressEvent.connect(mousePressEvent); \ No newline at end of file diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js index 7e379294ec..fc21caee1d 100644 --- a/examples/example/games/grabHockey.js +++ b/examples/example/games/grabHockey.js @@ -29,31 +29,38 @@ var tablePosition = { z: 0 } var isGrabbing = false; +var isGrabbingPaddle = false; var grabbedEntity = null; -var prevMouse = {}; +var prevMouse = {x: 0, y: 0}; var deltaMouse = { z: 0 } +var MAX_GRAB_DISTANCE = 100; var TABLE_SEARCH_RANGE = 10; var entityProps; var moveUpDown = false; var CLOSE_ENOUGH = 0.001; var FULL_STRENGTH = 1.0; -var SPRING_RATE = 1.5; +var SPRING_TIMESCALE = 0.05; +var ANGULAR_SPRING_TIMESCALE = 0.03; var DAMPING_RATE = 0.80; var ANGULAR_DAMPING_RATE = 0.40; var SCREEN_TO_METERS = 0.001; var currentPosition, currentVelocity, cameraEntityDistance, currentRotation; var grabHeight; +var initialVerticalGrabPosition; +var MAX_VERTICAL_ANGLE = Math.PI / 3; +var MIN_VERTICAL_ANGLE = - MAX_VERTICAL_ANGLE; var velocityTowardTarget, desiredVelocity, addedVelocity, newVelocity, dPosition, camYaw, distanceToTarget, targetPosition; +var grabOffset; var originalGravity = { x: 0, y: 0, z: 0 }; var shouldRotate = false; -var dQ, theta, axisAngle, dT; +var dQ, dT; var angularVelocity = { x: 0, y: 0, @@ -94,6 +101,34 @@ function nearLinePoint(targetPosition) { return Vec3.sum(handPosition, along); } +function xzPickRayIntersetion(pointOnPlane, mouseX, mouseY) { + var relativePoint = Vec3.subtract(pointOnPlane, Camera.getPosition()); + var pickRay = Camera.computePickRay(mouseX, mouseY); + if (Math.abs(pickRay.direction.y) > 0.001) { + var distance = relativePoint.y / pickRay.direction.y; + if (distance < 0.001) { + return pointOnPlane; + } + var pickInersection = Vec3.multiply(pickRay.direction, distance); + pickInersection = Vec3.sum(Camera.getPosition(), pickInersection); + return pickInersection; + } + // point and line are more-or-less co-planar: compute closest approach of pickRay and pointOnPlane + var distance = Vec3.dot(relativePoint, pickRay.direction); + var pickInersection = Vec3.multiply(pickRay.direction, distance); + return pickInersection; +} + +function forwardPickRayIntersection(pointOnPlane, mouseX, mouseY) { + var relativePoint = Vec3.subtract(pointOnPlane, Camera.getPosition()); + var pickRay = Camera.computePickRay(mouseX, mouseY); + var planeNormal = Quat.getFront(Camera.getOrientation()); + var distance = Vec3.dot(planeNormal, relativePoint); + var rayDistance = Vec3.dot(planeNormal, pickRay.direction); + var pickIntersection = Vec3.multiply(pickRay.direction, distance / rayDistance); + pickIntersection = Vec3.sum(pickIntersection, Camera.getPosition()) + return pickIntersection; +} function mousePressEvent(event) { if (!event.isLeftButton) { @@ -103,23 +138,35 @@ function mousePressEvent(event) { prevMouse.y = event.y; var pickRay = Camera.computePickRay(event.x, event.y); - var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking - if (!intersection.intersects) { + var pickResults = Entities.findRayIntersection(pickRay, true); // accurate picking + if (!pickResults.intersects) { return; } - if (intersection.properties.collisionsWillMove) { - grabbedEntity = intersection.entityID; + if (pickResults.properties.collisionsWillMove) { + grabbedEntity = pickResults.entityID; var props = Entities.getEntityProperties(grabbedEntity) - isGrabbing = true; originalGravity = props.gravity; - targetPosition = props.position; + var objectPosition = props.position; currentPosition = props.position; + if (Vec3.distance(currentPosition, Camera.getPosition()) > MAX_GRAB_DISTANCE) { + // don't allow grabs of things far away + return; + } + + isGrabbing = true; + isGrabbingPaddle = (props.name == "air-hockey-paddle-23j4h1jh82jsjfw91jf232n2k"); + currentVelocity = props.velocity; - updateDropLine(targetPosition); + updateDropLine(objectPosition); + + var pointOnPlane = xzPickRayIntersetion(objectPosition, event.x, event.y); + grabOffset = Vec3.subtract(pointOnPlane, objectPosition); + targetPosition = objectPosition; // remember the height of the object when first grabbed // we'll try to maintain this height during the rest of this grab grabHeight = currentPosition.y; + initialVerticalGrabPosition = currentPosition; Entities.editEntity(grabbedEntity, { gravity: { @@ -194,7 +241,6 @@ function mouseReleaseEvent() { } } -// new mouseMoveEvent function mouseMoveEvent(event) { if (isGrabbing) { // see if something added/restored gravity @@ -204,71 +250,78 @@ function mouseMoveEvent(event) { } if (shouldRotate) { + targetPosition = currentPosition; deltaMouse.x = event.x - prevMouse.x; - if (!moveUpDown) { - deltaMouse.z = event.y - prevMouse.y; - deltaMouse.y = 0; - } else { - deltaMouse.y = (event.y - prevMouse.y) * -1; - deltaMouse.z = 0; - } + deltaMouse.z = event.y - prevMouse.y; + deltaMouse.y = 0; + var transformedDeltaMouse = { x: deltaMouse.z, y: deltaMouse.x, z: deltaMouse.y }; - transformedDeltaMouse = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, camYaw, 0), transformedDeltaMouse); dQ = Quat.fromVec3Degrees(transformedDeltaMouse); - theta = 2 * Math.acos(dQ.w); - axisAngle = Quat.axis(dQ); - angularVelocity = Vec3.multiply((theta / dT), axisAngle); + var theta = 2 * Math.acos(dQ.w); + var axis = Quat.axis(dQ); + angularVelocity = Vec3.multiply((theta / ANGULAR_SPRING_TIMESCALE), axis); } else { - var relativePosition = Vec3.subtract(currentPosition, Camera.getPosition()); - if (relativePosition.y < 0) { - // grabee is below camera, so movement is valid - // compute intersectionPoint where mouse ray hits grabee's current x-z plane - var pickRay = Camera.computePickRay(event.x, event.y); - var mousePosition = pickRay.direction; - var length = relativePosition.y / mousePosition.y; - mousePosition = Vec3.multiply(mousePosition, length); - mousePosition = Vec3.sum(Camera.getPosition(), mousePosition); + if (moveUpDown) { + targetPosition = forwardPickRayIntersection(currentPosition, event.x, event.y); + grabHeight = targetPosition.y; + } else { + var pointOnPlane = xzPickRayIntersetion(currentPosition, event.x, event.y); + pointOnPlane = Vec3.subtract(pointOnPlane, grabOffset); - // translate mousePosition into local-frame - mousePosition = Vec3.subtract(mousePosition, tablePosition); + if (isGrabbingPaddle) { + // translate pointOnPlane into local-frame + pointOnPlane = Vec3.subtract(pointOnPlane, tablePosition); - // clamp local mousePosition to table field - if (mousePosition.x > fieldMaxOffset.x) { - mousePosition.x = fieldMaxOffset.x; - } else if (mousePosition.x < fieldMinOffset.x) { - mousePosition.x = fieldMinOffset.x; + // clamp local pointOnPlane to table field + if (pointOnPlane.x > fieldMaxOffset.x) { + pointOnPlane.x = fieldMaxOffset.x; + } else if (pointOnPlane.x < fieldMinOffset.x) { + pointOnPlane.x = fieldMinOffset.x; + } + if (pointOnPlane.z > fieldMaxOffset.z) { + pointOnPlane.z = fieldMaxOffset.z; + } else if (pointOnPlane.z < fieldMinOffset.z) { + pointOnPlane.z = fieldMinOffset.z; + } + + // clamp to rotated square (for cut corners) + var rotation = Quat.angleAxis(45, { x:0, y:1, z:0 }); + pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); + if (pointOnPlane.x > halfCornerBoxWidth) { + pointOnPlane.x = halfCornerBoxWidth; + } else if (pointOnPlane.x < -halfCornerBoxWidth) { + pointOnPlane.x = -halfCornerBoxWidth; + } + if (pointOnPlane.z > halfCornerBoxWidth) { + pointOnPlane.z = halfCornerBoxWidth; + } else if (pointOnPlane.z < -halfCornerBoxWidth) { + pointOnPlane.z = -halfCornerBoxWidth; + } + // rotate back into local frame + rotation.y = -rotation.y; + pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); + + // translate into world-frame + pointOnPlane = Vec3.sum(tablePosition, pointOnPlane); + } else { + // we're grabbing a non-paddle object + + // clamp distance + var relativePosition = Vec3.subtract(pointOnPlane, Camera.getPosition()); + var length = Vec3.length(relativePosition); + if (length > MAX_GRAB_DISTANCE) { + relativePosition = Vec3.multiply(relativePosition, MAX_GRAB_DISTANCE / length); + pointOnPlane = Vec3.sum(relativePosition, Camera.getPosition()); + } } - if (mousePosition.z > fieldMaxOffset.z) { - mousePosition.z = fieldMaxOffset.z; - } else if (mousePosition.z < fieldMinOffset.z) { - mousePosition.z = fieldMinOffset.z; - } - - // clamp to rotated square (for cut corners) - var rotation = Quat.angleAxis(45, { x:0, y:1, z:0 }); - mousePosition = Vec3.multiplyQbyV(rotation, mousePosition); - if (mousePosition.x > halfCornerBoxWidth) { - mousePosition.x = halfCornerBoxWidth; - } else if (mousePosition.x < -halfCornerBoxWidth) { - mousePosition.x = -halfCornerBoxWidth; - } - if (mousePosition.z > halfCornerBoxWidth) { - mousePosition.z = halfCornerBoxWidth; - } else if (mousePosition.z < -halfCornerBoxWidth) { - mousePosition.z = -halfCornerBoxWidth; - } - // rotate back into local frame - rotation.y = -rotation.y; - mousePosition = Vec3.multiplyQbyV(rotation, mousePosition); - // translate into world-frame - mousePosition = Vec3.sum(tablePosition, mousePosition); - - mousePosition.y = grabHeight; - targetPosition = mousePosition; + // clamp to grabHeight + pointOnPlane.y = grabHeight; + targetPosition = pointOnPlane; + initialVerticalGrabPosition = targetPosition; } } } @@ -297,57 +350,32 @@ function keyPressEvent(event) { function update(deltaTime) { dT = deltaTime; if (isGrabbing) { - entityProps = Entities.getEntityProperties(grabbedEntity); currentPosition = entityProps.position; - currentVelocity = entityProps.velocity; - currentRotation = entityProps.rotation; - - var dPosition = Vec3.subtract(targetPosition, currentPosition); - - distanceToTarget = Vec3.length(dPosition); - if (distanceToTarget > CLOSE_ENOUGH) { - // compute current velocity in the direction we want to move - velocityTowardTarget = Vec3.dot(currentVelocity, Vec3.normalize(dPosition)); - velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), velocityTowardTarget); - // compute the speed we would like to be going toward the target position - - desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); - // compute how much we want to add to the existing velocity - addedVelocity = Vec3.subtract(desiredVelocity, velocityTowardTarget); - // If target is too far, roll off the force as inverse square of distance - if (distanceToTarget / cameraEntityDistance > FULL_STRENGTH) { - addedVelocity = Vec3.multiply(addedVelocity, Math.pow(FULL_STRENGTH / distanceToTarget, 2.0)); - } - newVelocity = Vec3.sum(currentVelocity, addedVelocity); - // Add Damping - newVelocity = Vec3.subtract(newVelocity, Vec3.multiply(newVelocity, DAMPING_RATE)); - // Update entity - } else { - newVelocity = { - x: 0, - y: 0, - z: 0 - }; - } if (shouldRotate) { angularVelocity = Vec3.subtract(angularVelocity, Vec3.multiply(angularVelocity, ANGULAR_DAMPING_RATE)); + Entities.editEntity(grabbedEntity, { angularVelocity: angularVelocity, }); } else { - angularVelocity = entityProps.angularVelocity; + var dPosition = Vec3.subtract(targetPosition, currentPosition); + var delta = Vec3.length(dPosition); + if (delta > CLOSE_ENOUGH) { + var MAX_POSITION_DELTA = 0.50; + if (delta > MAX_POSITION_DELTA) { + dPosition = Vec3.multiply(dPosition, MAX_POSITION_DELTA / delta); + } + // desired speed is proportional to displacement by the inverse of timescale + // (for critically damped motion) + newVelocity = Vec3.multiply(dPosition, 1.0 / SPRING_TIMESCALE); + } else { + newVelocity = { + x: 0, + y: 0, + z: 0 + }; + } + Entities.editEntity(grabbedEntity, { velocity: newVelocity, }); } - // enforce that grabee's rotation is only about y-axis while being grabbed - currentRotation.x = 0; - currentRotation.z = 0; - currentRotation.y = Math.sqrt(1.0 - currentRotation.w * currentRotation.w); - // Hrm... slamming the currentRotation doesn't seem to work - - Entities.editEntity(grabbedEntity, { - position: currentPosition, - rotation: currentRotation, - velocity: newVelocity, - angularVelocity: angularVelocity - }); updateDropLine(targetPosition); } } diff --git a/examples/grab.js b/examples/grab.js index f1e1b6571c..7ed69e9664 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -36,8 +36,6 @@ var angularVelocity = { z: 0 }; -var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); -var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); var DROP_DISTANCE = 5.0; var DROP_COLOR = { @@ -90,10 +88,6 @@ function mousePressEvent(event) { gravity: {x: 0, y: 0, z: 0} }); - Audio.playSound(grabSound, { - position: props.position, - volume: 0.4 - }); } } @@ -135,11 +129,6 @@ function mouseReleaseEvent() { }); targetPosition = null; - Audio.playSound(grabSound, { - position: entityProps.position, - volume: 0.25 - }); - } } diff --git a/interface/resources/images/address-bar-error-close.svg b/interface/resources/images/address-bar-error-close.svg new file mode 100644 index 0000000000..45a4dd4635 --- /dev/null +++ b/interface/resources/images/address-bar-error-close.svg @@ -0,0 +1,50 @@ + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/images/address-bar-error-icon.svg b/interface/resources/images/address-bar-error-icon.svg new file mode 100644 index 0000000000..f2453fb0d4 --- /dev/null +++ b/interface/resources/images/address-bar-error-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/interface/resources/images/address-bar-submit-active.svg b/interface/resources/images/address-bar-submit-active.svg deleted file mode 100644 index 313b366033..0000000000 --- a/interface/resources/images/address-bar-submit-active.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/interface/resources/images/address-bar-submit.svg b/interface/resources/images/address-bar-submit.svg deleted file mode 100644 index df4d7e90f6..0000000000 --- a/interface/resources/images/address-bar-submit.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/interface/resources/images/address-bar.svg b/interface/resources/images/address-bar.svg new file mode 100644 index 0000000000..0a472cbe4e --- /dev/null +++ b/interface/resources/images/address-bar.svg @@ -0,0 +1,72 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index e12452472a..3893c26f3c 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -1,91 +1,145 @@ +// +// AddressBarDialog.qml +// +// Created by Austin Davis on 2015/04/14 +// Copyright 2015 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 +// + import Hifi 1.0 import QtQuick 2.3 +import QtQuick.Controls 1.2 import "controls" import "styles" -Dialog { +Item { id: root HifiConstants { id: hifi } - - title: "Go to..." + objectName: "AddressBarDialog" - contentImplicitWidth: addressBarDialog.implicitWidth - contentImplicitHeight: addressBarDialog.implicitHeight - destroyOnCloseButton: false + property int animationDuration: hifi.effects.fadeInDuration + property bool destroyOnInvisible: false - onVisibleChanged: { - if (!visible) { - reset(); - } + implicitWidth: addressBarDialog.implicitWidth + implicitHeight: addressBarDialog.implicitHeight + + x: parent ? parent.width / 2 - width / 2 : 0 + y: parent ? parent.height / 2 - height / 2 : 0 + property int maximumX: parent ? parent.width - width : 0 + property int maximumY: parent ? parent.height - height : 0 + + AddressBarDialog { + id: addressBarDialog + + implicitWidth: backgroundImage.width + implicitHeight: backgroundImage.height + + Image { + id: backgroundImage + + source: "../images/address-bar.svg" + width: 576 + height: 80 + property int inputAreaHeight: 56 // Height of the background's input area + property int inputAreaStep: (height - inputAreaHeight) / 2 + + TextInput { + id: addressLine + + anchors { + fill: parent + leftMargin: parent.height + hifi.layout.spacing * 2 + rightMargin: hifi.layout.spacing * 2 + topMargin: parent.inputAreaStep + hifi.layout.spacing + bottomMargin: parent.inputAreaStep + hifi.layout.spacing + + } + + font.pointSize: 15 + helperText: "Go to: place, @user, /path, network address" + + onAccepted: { + event.accepted = true // Generates erroneous error in program log, "ReferenceError: event is not defined". + addressBarDialog.loadAddress(addressLine.text) + } + } + + MouseArea { + // Drag the icon + width: parent.height + height: parent.height + x: 0 + y: 0 + drag { + target: root + minimumX: -parent.inputAreaStep + minimumY: -parent.inputAreaStep + maximumX: root.parent ? root.maximumX : 0 + maximumY: root.parent ? root.maximumY + parent.inputAreaStep : 0 + } + } + + MouseArea { + // Drag the input rectangle + width: parent.width - parent.height + height: parent.inputAreaHeight + x: parent.height + y: parent.inputAreaStep + drag { + target: root + minimumX: -parent.inputAreaStep + minimumY: -parent.inputAreaStep + maximumX: root.parent ? root.maximumX : 0 + maximumY: root.parent ? root.maximumY + parent.inputAreaStep : 0 + } + } + } } + // The UI enables an object, rather than manipulating its visibility, so that we can do animations in both directions. + // Because visibility and enabled are booleans, they cannot be animated. So when enabled is changed, we modify a property + // that can be animated, like scale or opacity, and then when the target animation value is reached, we can modify the + // visibility. + enabled: false + opacity: 0.0 + onEnabledChanged: { + opacity = enabled ? 1.0 : 0.0 if (enabled) { addressLine.forceActiveFocus(); } } - onParentChanged: { - if (enabled && visible) { - addressLine.forceActiveFocus(); + + Behavior on opacity { + // Animate opacity. + NumberAnimation { + duration: animationDuration + easing.type: Easing.OutCubic } } + onOpacityChanged: { + // Once we're transparent, disable the dialog's visibility. + visible = (opacity != 0.0) + } + + onVisibleChanged: { + if (!visible) { + reset() + + // Some dialogs should be destroyed when they become invisible. + if (destroyOnInvisible) { + destroy() + } + } + + } + function reset() { addressLine.text = "" - goButton.source = "../images/address-bar-submit.svg" - } - - AddressBarDialog { - id: addressBarDialog - // The client area - x: root.clientX - y: root.clientY - implicitWidth: 512 - implicitHeight: border.height + hifi.layout.spacing * 4 - - - Border { - id: border - height: 64 - anchors.left: parent.left - anchors.leftMargin: hifi.layout.spacing * 2 - anchors.right: goButton.left - anchors.rightMargin: hifi.layout.spacing - anchors.verticalCenter: parent.verticalCenter - TextInput { - id: addressLine - anchors.fill: parent - helperText: "domain, location, @user, /x,y,z" - anchors.margins: hifi.layout.spacing - onAccepted: { - event.accepted - addressBarDialog.loadAddress(addressLine.text) - } - } - } - - Image { - id: goButton - width: 32 - height: 32 - anchors.right: parent.right - anchors.rightMargin: hifi.layout.spacing * 2 - source: "../images/address-bar-submit.svg" - anchors.verticalCenter: parent.verticalCenter - - MouseArea { - anchors.fill: parent - onClicked: { - parent.source = "../images/address-bar-submit-active.svg" - addressBarDialog.loadAddress(addressLine.text) - } - } - } - } - - Keys.onEscapePressed: { - enabled = false; } function toggleOrGo() { @@ -95,8 +149,22 @@ Dialog { addressBarDialog.loadAddress(addressLine.text) } } - + + Keys.onEscapePressed: { + enabled = false + } + + Keys.onPressed: { + switch(event.key) { + case Qt.Key_W: + if (event.modifiers == Qt.ControlModifier) { + event.accepted = true + enabled = false + } + break + } + } + Keys.onReturnPressed: toggleOrGo() Keys.onEnterPressed: toggleOrGo() } - diff --git a/interface/resources/qml/ErrorDialog.qml b/interface/resources/qml/ErrorDialog.qml new file mode 100644 index 0000000000..c0f8132f14 --- /dev/null +++ b/interface/resources/qml/ErrorDialog.qml @@ -0,0 +1,152 @@ +// +// ErrorDialog.qml +// +// Created by David Rowe on 30 May 2015 +// Copyright 2015 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 +import "controls" +import "styles" + +Item { + id: root + HifiConstants { id: hifi } + + property int animationDuration: hifi.effects.fadeInDuration + property bool destroyOnInvisible: true + + Component.onCompleted: { + enabled = true + } + + onParentChanged: { + if (visible && enabled) { + forceActiveFocus(); + } + } + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + x: parent ? parent.width / 2 - width / 2 : 0 + y: parent ? parent.height / 2 - height / 2 : 0 + + Hifi.ErrorDialog { + id: content + + implicitWidth: box.width + implicitHeight: icon.height + hifi.layout.spacing * 2 + + Border { + id: box + + width: 512 + color: "#ebebeb" + radius: 2 + border.width: 1 + border.color: "#000000" + + Image { + id: icon + + source: "../images/address-bar-error-icon.svg" + width: 40 + height: 40 + anchors { + left: parent.left + leftMargin: hifi.layout.spacing + verticalCenter: parent.verticalCenter + } + } + + Text { + id: messageText + + font.pointSize: 10 + font.weight: Font.Bold + + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + + text: content.text + } + + Image { + source: "../images/address-bar-error-close.svg" + width: 20 + height: 20 + anchors { + right: parent.right + rightMargin: hifi.layout.spacing * 2 + verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + onClicked: { + content.accept(); + } + } + } + } + } + + // The UI enables an object, rather than manipulating its visibility, so that we can do animations in both directions. + // Because visibility and enabled are booleans, they cannot be animated. So when enabled is changed, we modify a property + // that can be animated, like scale or opacity, and then when the target animation value is reached, we can modify the + // visibility. + enabled: false + opacity: 0.0 + + onEnabledChanged: { + opacity = enabled ? 1.0 : 0.0 + } + + Behavior on opacity { + // Animate opacity. + NumberAnimation { + duration: animationDuration + easing.type: Easing.OutCubic + } + } + + onOpacityChanged: { + // Once we're transparent, disable the dialog's visibility. + visible = (opacity != 0.0) + } + + onVisibleChanged: { + if (!visible) { + // Some dialogs should be destroyed when they become invisible. + if (destroyOnInvisible) { + destroy() + } + } + } + + Keys.onPressed: { + if (event.modifiers === Qt.ControlModifier) + switch (event.key) { + case Qt.Key_W: + event.accepted = true + content.accept() + break + } else switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + content.accept() + break + } + } +} diff --git a/interface/resources/qml/controls/Dialog.qml b/interface/resources/qml/controls/Dialog.qml index f32b4e2e66..aa14e2fcba 100644 --- a/interface/resources/qml/controls/Dialog.qml +++ b/interface/resources/qml/controls/Dialog.qml @@ -31,7 +31,7 @@ DialogBase { * Support for animating the dialog in and out. */ enabled: false - scale: 0.0 + opacity: 1.0 // The offscreen UI will enable an object, rather than manipulating it's // visibility, so that we can do animations in both directions. Because @@ -40,20 +40,20 @@ DialogBase { // opacity, and then when the target animation value is reached, we can // modify the visibility onEnabledChanged: { - scale = enabled ? 1.0 : 0.0 + opacity = enabled ? 1.0 : 0.0 } // The actual animator - Behavior on scale { + Behavior on opacity { NumberAnimation { - duration: root.animationDuration - easing.type: Easing.InOutBounce + duration: animationDuration + easing.type: Easing.OutCubic } } - // Once we're scaled to 0, disable the dialog's visibility - onScaleChanged: { - visible = (scale != 0.0); + // Once we're transparent, disable the dialog's visibility + onOpacityChanged: { + visible = (opacity != 0.0); } // Some dialogs should be destroyed when they become invisible, diff --git a/interface/resources/qml/styles/HifiConstants.qml b/interface/resources/qml/styles/HifiConstants.qml index d24e9ca9be..702a396afb 100644 --- a/interface/resources/qml/styles/HifiConstants.qml +++ b/interface/resources/qml/styles/HifiConstants.qml @@ -56,6 +56,6 @@ Item { QtObject { id: effects - readonly property int fadeInDuration: 400 + readonly property int fadeInDuration: 300 } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2b3029bfed..fdb7277a0c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -55,40 +55,41 @@ #include #include -#include #include +#include #include #include #include #include #include +#include #include #include #include -#include +#include #include #include +#include #include #include #include +#include #include #include -#include #include #include #include #include #include #include +#include #include #include #include #include #include #include -#include -#include -#include +#include #include "Application.h" #include "AudioClient.h" @@ -807,6 +808,7 @@ void Application::initializeGL() { void Application::initializeUi() { AddressBarDialog::registerType(); + ErrorDialog::registerType(); LoginDialog::registerType(); MessageDialog::registerType(); VrMenu::registerType(); @@ -3105,21 +3107,27 @@ PickRay Application::computePickRay(float x, float y) const { QImage Application::renderAvatarBillboard(RenderArgs* renderArgs) { auto primaryFramebuffer = DependencyManager::get()->getPrimaryFramebuffer(); glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFramebuffer)); - + + // clear the alpha channel so the background is transparent + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + // the "glow" here causes an alpha of one Glower glower(renderArgs); - const int BILLBOARD_SIZE = 64; // TODO: Pass a RenderArgs to renderAvatarBillboard renderRearViewMirror(renderArgs, QRect(0, _glWidget->getDeviceHeight() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true); - + QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32); glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); - + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); - + return image; } @@ -3301,7 +3309,7 @@ namespace render { } -void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly) { +void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly, bool billboard) { activeRenderingThread = QThread::currentThread(); PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("display"); @@ -3481,7 +3489,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se } - { + if (!billboard) { DependencyManager::get()->setAmbientLightMode(getRenderAmbientLight()); auto skyStage = DependencyManager::get()->getSkyStage(); DependencyManager::get()->setGlobalLight(skyStage->getSunLight()->getDirection(), skyStage->getSunLight()->getColor(), skyStage->getSunLight()->getIntensity(), skyStage->getSunLight()->getAmbientIntensity()); @@ -3673,7 +3681,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi // render rear mirror view glPushMatrix(); - displaySide(renderArgs, _mirrorCamera, true); + displaySide(renderArgs, _mirrorCamera, true, billboard); glPopMatrix(); if (!billboard) { diff --git a/interface/src/Application.h b/interface/src/Application.h index aaf831a511..1a19800869 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -271,7 +271,7 @@ public: QImage renderAvatarBillboard(RenderArgs* renderArgs); - void displaySide(RenderArgs* renderArgs, Camera& whichCamera, bool selfAvatarOnly = false); + void displaySide(RenderArgs* renderArgs, Camera& whichCamera, bool selfAvatarOnly = false, bool billboard = false); /// Stores the current modelview matrix as the untranslated view matrix to use for transforms and the supplied vector as /// the view matrix translation. diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 837702d253..3c3a843586 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -36,12 +36,10 @@ void AddressBarDialog::loadAddress(const QString& address) { } void AddressBarDialog::displayAddressOfflineMessage() { - OffscreenUi::information("Address offline", - "That user or place is currently offline."); + OffscreenUi::error("That user or place is currently offline"); } void AddressBarDialog::displayAddressNotFoundMessage() { - OffscreenUi::information("Address not found", - "There is no address information for that user or place."); + OffscreenUi::error("There is no address information for that user or place"); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index af73c1cc02..9a8966dbc0 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1178,7 +1178,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT const float energy = mass * linearVelocity * linearVelocity / 2.0f; const glm::vec3 position = collision.contactPoint; const float COLLISION_ENERGY_AT_FULL_VOLUME = 0.5f; - const float COLLISION_MINIMUM_VOLUME = 0.001f; + const float COLLISION_MINIMUM_VOLUME = 0.005f; const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME); if (energyFactorOfFull < COLLISION_MINIMUM_VOLUME) { return; @@ -1194,7 +1194,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT } // This is a hack. Quiet sound aren't really heard at all, so we compress everything to the range [1-c, 1], if we play it all. - const float COLLISION_SOUND_COMPRESSION_RANGE = 0.7f; + const float COLLISION_SOUND_COMPRESSION_RANGE = 1.0f; // This section could be removed when the value is 1, but let's see how it goes. float volume = energyFactorOfFull; volume = (volume * COLLISION_SOUND_COMPRESSION_RANGE) + (1.0f - COLLISION_SOUND_COMPRESSION_RANGE); @@ -1225,7 +1225,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons return; } // Don't respond to small continuous contacts. - const float COLLISION_MINUMUM_PENETRATION = 0.005f; + const float COLLISION_MINUMUM_PENETRATION = 0.002f; if ((collision.type != CONTACT_EVENT_TYPE_START) && (glm::length(collision.penetration) < COLLISION_MINUMUM_PENETRATION)) { return; } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 462ff94f5f..c4930f1225 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "EntityTreeRenderer.h" #include "EntitiesRendererLogging.h" @@ -136,27 +137,67 @@ bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) { return ready; } +class RenderableModelEntityItemMeta { +public: + RenderableModelEntityItemMeta(EntityItemPointer entity) : entity(entity){ } + typedef render::Payload Payload; + typedef Payload::DataPointer Pointer; + + EntityItemPointer entity; +}; + +namespace render { + template <> const ItemKey payloadGetKey(const RenderableModelEntityItemMeta::Pointer& payload) { + return ItemKey::Builder::opaqueShape(); + } + + template <> const Item::Bound payloadGetBound(const RenderableModelEntityItemMeta::Pointer& payload) { + if (payload && payload->entity) { + return payload->entity->getAABox(); + } + return render::Item::Bound(); + } + template <> void payloadRender(const RenderableModelEntityItemMeta::Pointer& payload, RenderArgs* args) { + if (args) { + args->_elementsTouched++; + if (payload && payload->entity) { + payload->entity->render(args); + } + } + } +} + bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { + _myMetaItem = scene->allocateID(); + + auto renderData = RenderableModelEntityItemMeta::Pointer(new RenderableModelEntityItemMeta(self)); + auto renderPayload = render::PayloadPointer(new RenderableModelEntityItemMeta::Payload(renderData)); + + pendingChanges.resetItem(_myMetaItem, renderPayload); + if (_model) { return _model->addToScene(scene, pendingChanges); } - return false; + + return true; } void RenderableModelEntityItem::removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { + pendingChanges.removeItem(_myMetaItem); if (_model) { _model->removeFromScene(scene, pendingChanges); } } + +// NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items, and it handles +// the per frame simulation/update that might be required if the models properties changed. void RenderableModelEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RMEIrender"); assert(getType() == EntityTypes::Model); - bool drawAsModel = hasModel(); - glm::vec3 position = getPosition(); glm::vec3 dimensions = getDimensions(); @@ -168,8 +209,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { highlightSimulationOwnership = (getSimulatorID() == myNodeID); } - bool didDraw = false; - if (drawAsModel && !highlightSimulationOwnership) { + if (hasModel()) { remapTextures(); { float alpha = getLocalRenderAlpha(); @@ -212,27 +252,14 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } _needsInitialSimulation = false; } - - if (_model->isActive()) { - // TODO: this is the majority of model render time. And rendering of a cube model vs the basic Box render - // is significantly more expensive. Is there a way to call this that doesn't cost us as much? - PerformanceTimer perfTimer("model->render"); - // filter out if not needed to render - if (args && (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE)) { - if (movingOrAnimating) { - _model->renderInScene(alpha, args); - didDraw = true; - } - } else { - _model->renderInScene(alpha, args); - didDraw = true; - } - } } } - } - if (!didDraw) { + if (highlightSimulationOwnership) { + glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); + RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); + } + } else { glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 55044e5d4d..837890a960 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -77,6 +77,8 @@ private: QStringList _originalTextures; bool _originalTexturesRead; QVector> _points; + + render::ItemID _myMetaItem; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 1fa960c100..e6b5c89899 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -480,17 +480,21 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef bytesRead += encodedUpdateDelta.size(); // Newer bitstreams will have a last simulated and a last updated value + quint64 lastSimulatedFromBufferAdjusted = now; if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { // last simulated is stored as ByteCountCoded delta from lastEdited QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; quint64 simulatedDelta = simulatedDeltaCoder; if (overwriteLocalData) { - _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that + lastSimulatedFromBufferAdjusted = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that + if (lastSimulatedFromBufferAdjusted > now) { + lastSimulatedFromBufferAdjusted = now; + } #ifdef WANT_DEBUG - qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + qCDebug(entities) << " lastSimulatedFromBufferAdjusted:" << debugTime(lastSimulatedFromBufferAdjusted, now); #endif } encodedSimulatedDelta = simulatedDeltaCoder; // determine true length @@ -606,8 +610,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // use our simulation helper routine to get a best estimate of where the entity should be. const float MIN_TIME_SKIP = 0.0f; const float MAX_TIME_SKIP = 1.0f; // in seconds - float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), - MIN_TIME_SKIP, MAX_TIME_SKIP); + float skipTimeForward = glm::clamp((float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND), + MIN_TIME_SKIP, MAX_TIME_SKIP); if (skipTimeForward > 0.0f) { #ifdef WANT_DEBUG qCDebug(entities) << "skipTimeForward:" << skipTimeForward; @@ -617,19 +621,22 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // we don't want the side effect of flag setting. simulateKinematicMotion(skipTimeForward, false); } - _lastSimulated = now; } auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); - if (overwriteLocalData && _simulatorID == myNodeID && !_simulatorID.isNull()) { - // we own the simulation, so we keep our transform+velocities and remove any related dirty flags - // rather than accept the values in the packet - setPosition(savePosition); - setRotation(saveRotation); - _velocity = saveVelocity; - _angularVelocity = saveAngularVelocity; - _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); + if (overwriteLocalData) { + if (_simulatorID == myNodeID && !_simulatorID.isNull()) { + // we own the simulation, so we keep our transform+velocities and remove any related dirty flags + // rather than accept the values in the packet + setPosition(savePosition); + setRotation(saveRotation); + _velocity = saveVelocity; + _angularVelocity = saveAngularVelocity; + _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); + } else { + _lastSimulated = now; + } } return bytesRead; @@ -919,6 +926,21 @@ EntityItemProperties EntityItem::getProperties() const { return properties; } +void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) const { + // a TerseUpdate includes the transform and its derivatives + properties._position = getPosition(); + properties._velocity = _velocity; + properties._rotation = getRotation(); + properties._angularVelocity = _angularVelocity; + properties._acceleration = _acceleration; + + properties._positionChanged = true; + properties._velocityChanged = true; + properties._rotationChanged = true; + properties._angularVelocityChanged = true; + properties._accelerationChanged = true; +} + bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 93d2a7f035..de34e89ea4 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -366,6 +366,8 @@ public: quint64 getLastEditedFromRemote() { return _lastEditedFromRemote; } + void getAllTerseUpdateProperties(EntityItemProperties& properties) const; + protected: static bool _sendPhysicsUpdates; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 856c1a1cb4..583cf15b9c 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1156,4 +1156,7 @@ AABox EntityItemProperties::getAABox() const { return AABox(rotatedExtentsRelativeToRegistrationPoint); } - +bool EntityItemProperties::hasTerseUpdateChanges() const { + // a TerseUpdate includes the transform and its derivatives + return _positionChanged || _velocityChanged || _rotationChanged || _angularVelocityChanged || _accelerationChanged; +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 26c26bd474..dbe2e926c9 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -195,6 +195,8 @@ public: void setVoxelDataDirty() { _voxelDataChanged = true; } + bool hasTerseUpdateChanges() const; + private: QUuid _id; bool _idSet; @@ -215,6 +217,7 @@ private: QStringList _textureNames; glm::vec3 _naturalDimensions; }; + Q_DECLARE_METATYPE(EntityItemProperties); QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); QScriptValue EntityItemNonDefaultPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 54d2ea705e..d684fbf2fc 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -146,7 +146,17 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& EntityItemProperties modifiedProperties = properties; entity->setLastBroadcast(usecTimestampNow()); modifiedProperties.setType(entity->getType()); - bidForSimulationOwnership(modifiedProperties); + if (modifiedProperties.hasTerseUpdateChanges()) { + // we make a bid for (or assert) our simulation ownership + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + modifiedProperties.setSimulatorID(myNodeID); + + if (entity->getSimulatorID() == myNodeID) { + // we think we already own simulation, so make sure we send ALL TerseUpdate properties + entity->getAllTerseUpdateProperties(modifiedProperties); + } + } queueEntityMessage(PacketTypeEntityEdit, entityID, modifiedProperties); return id; } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 26cd3f1425..44aa5bc644 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -505,9 +505,13 @@ void NodeList::pingPunchForDomainServer() { } else { if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat + qCDebug(networking) << "No ping replies received from domain-server with ID" + << uuidStringWithoutCurlyBraces(_domainHandler.getICEClientID()) << "-" << "re-sending ICE query."; _domainHandler.getICEPeer().softReset(); handleICEConnectionToDomainServer(); + + return; } } diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index 2694314b57..80f0023970 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -27,14 +27,17 @@ using namespace std; -ViewFrustum::ViewFrustum() { -} - void ViewFrustum::setOrientation(const glm::quat& orientationAsQuaternion) { _orientation = orientationAsQuaternion; _right = glm::vec3(orientationAsQuaternion * glm::vec4(IDENTITY_RIGHT, 0.0f)); _up = glm::vec3(orientationAsQuaternion * glm::vec4(IDENTITY_UP, 0.0f)); _direction = glm::vec3(orientationAsQuaternion * glm::vec4(IDENTITY_FRONT, 0.0f)); + _view = glm::translate(mat4(), _position) * glm::mat4_cast(_orientation); +} + +void ViewFrustum::setPosition(const glm::vec3& position) { + _position = position; + _view = glm::translate(mat4(), _position) * glm::mat4_cast(_orientation); } // Order cooresponds to the order defined in the BoxVertex enum. diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index 0422120e51..cd1e010818 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -36,11 +36,9 @@ const float DEFAULT_FAR_CLIP = (float)TREE_SCALE; class ViewFrustum { public: - ViewFrustum(); - // setters for camera attributes - void setPosition(const glm::vec3& p) { _position = p; } - void setOrientation(const glm::quat& orientationAsQuaternion); + void setPosition(const glm::vec3& position); + void setOrientation(const glm::quat& orientation); // getters for camera attributes const glm::vec3& getPosition() const { return _position; } @@ -54,7 +52,8 @@ public: void getFocalLength(float focalLength) { _focalLength = focalLength; } // getters for lens attributes - const glm::mat4 getProjection() const { return _projection; }; + const glm::mat4& getProjection() const { return _projection; } + const glm::mat4& getView() const { return _view; } float getWidth() const { return _width; } float getHeight() const { return _height; } float getFieldOfView() const { return _fieldOfView; } @@ -120,6 +119,7 @@ private: // camera location/orientation attributes glm::vec3 _position; // the position in world-frame glm::quat _orientation; + glm::mat4 _view; // Lens attributes glm::mat4 _projection; diff --git a/libraries/render-utils/src/GlWindow.cpp b/libraries/render-utils/src/GlWindow.cpp new file mode 100644 index 0000000000..ec294dca5e --- /dev/null +++ b/libraries/render-utils/src/GlWindow.cpp @@ -0,0 +1,66 @@ +// +// Created by Bradley Austin Davis on 2015/05/21 +// 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 "GlWindow.h" + +#include +#include +#include + +GlWindow::GlWindow(QOpenGLContext* shareContext) : GlWindow(getDefaultOpenGlSurfaceFormat(), shareContext) { +} + +GlWindow::GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext) { + setSurfaceType(QSurface::OpenGLSurface); + setFormat(format); + _context = new QOpenGLContext; + _context->setFormat(format); + if (shareContext) { + _context->setShareContext(shareContext); + } + _context->create(); +} + +GlWindow::~GlWindow() { +#ifdef DEBUG + if (_logger) { + makeCurrent(); + delete _logger; + _logger = nullptr; + } +#endif + _context->doneCurrent(); + _context->deleteLater(); + _context = nullptr; +} + + +void GlWindow::makeCurrent() { + _context->makeCurrent(this); +#ifdef DEBUG + if (!_logger) { + _logger = new QOpenGLDebugLogger(this); + if (_logger->initialize()) { + connect(_logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage& message) { + qDebug() << message; + }); + _logger->disableMessages(QOpenGLDebugMessage::AnySource, QOpenGLDebugMessage::AnyType, QOpenGLDebugMessage::NotificationSeverity); + _logger->startLogging(QOpenGLDebugLogger::LoggingMode::SynchronousLogging); + } + } +#endif +} + +void GlWindow::doneCurrent() { + _context->doneCurrent(); +} + +void GlWindow::swapBuffers() { + _context->swapBuffers(this); +} + diff --git a/libraries/render-utils/src/GlWindow.h b/libraries/render-utils/src/GlWindow.h new file mode 100644 index 0000000000..57108c6e37 --- /dev/null +++ b/libraries/render-utils/src/GlWindow.h @@ -0,0 +1,33 @@ +// +// Created by Bradley Austin Davis on 2015/05/21 +// 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 +// + +#pragma once +#ifndef hifi_GlWindow_h +#define hifi_GlWindow_h + +#include + +class QOpenGLContext; +class QOpenGLDebugLogger; + +class GlWindow : public QWindow { +public: + GlWindow(QOpenGLContext* shareContext = nullptr); + GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext = nullptr); + virtual ~GlWindow(); + void makeCurrent(); + void doneCurrent(); + void swapBuffers(); +private: + QOpenGLContext* _context{ nullptr }; +#ifdef DEBUG + QOpenGLDebugLogger* _logger{ nullptr }; +#endif +}; + +#endif diff --git a/libraries/render-utils/src/OffscreenGlCanvas.cpp b/libraries/render-utils/src/OffscreenGlCanvas.cpp index 0e5d4928d8..a3025bc3f6 100644 --- a/libraries/render-utils/src/OffscreenGlCanvas.cpp +++ b/libraries/render-utils/src/OffscreenGlCanvas.cpp @@ -11,6 +11,7 @@ #include "OffscreenGlCanvas.h" +#include OffscreenGlCanvas::OffscreenGlCanvas() { } @@ -27,16 +28,35 @@ void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) { format.setMajorVersion(4); format.setMinorVersion(1); format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); +#ifdef DEBUG + format.setOption(QSurfaceFormat::DebugContext); +#endif _context.setFormat(format); } _context.create(); _offscreenSurface.setFormat(_context.format()); _offscreenSurface.create(); + } bool OffscreenGlCanvas::makeCurrent() { - return _context.makeCurrent(&_offscreenSurface); + bool result = _context.makeCurrent(&_offscreenSurface); + +#ifdef DEBUG + if (result && !_logger) { + _logger = new QOpenGLDebugLogger(this); + if (_logger->initialize()) { + connect(_logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage& message) { + qDebug() << message; + }); + _logger->disableMessages(QOpenGLDebugMessage::AnySource, QOpenGLDebugMessage::AnyType, QOpenGLDebugMessage::NotificationSeverity); + _logger->startLogging(QOpenGLDebugLogger::LoggingMode::SynchronousLogging); + } + } +#endif + + return result; } void OffscreenGlCanvas::doneCurrent() { diff --git a/libraries/render-utils/src/OffscreenGlCanvas.h b/libraries/render-utils/src/OffscreenGlCanvas.h index 5a64a4cf10..399737ddb8 100644 --- a/libraries/render-utils/src/OffscreenGlCanvas.h +++ b/libraries/render-utils/src/OffscreenGlCanvas.h @@ -15,16 +15,24 @@ #include #include +class QOpenGLDebugLogger; + class OffscreenGlCanvas : public QObject { public: OffscreenGlCanvas(); void create(QOpenGLContext* sharedContext = nullptr); bool makeCurrent(); void doneCurrent(); + QOpenGLContext* getContext() { + return &_context; + } protected: QOpenGLContext _context; QOffscreenSurface _offscreenSurface; +#ifdef DEBUG + QOpenGLDebugLogger* _logger{ nullptr }; +#endif }; diff --git a/libraries/render-utils/src/OffscreenGlContext.cpp b/libraries/render-utils/src/OffscreenGlContext.cpp deleted file mode 100644 index 96f0a93c3a..0000000000 --- a/libraries/render-utils/src/OffscreenGlContext.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// -// OffscreenGlCanvas.cpp -// interface/src/renderer -// -// Created by Bradley Austin Davis on 2014/04/09. -// Copyright 2015 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 "OffscreenGlContext.h" - -OffscreenGlContext::OffscreenGlContext() { -} - -void OffscreenGlContext::create(QOpenGLContext * sharedContext) { - QSurfaceFormat format; - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - format.setMajorVersion(4); - format.setMinorVersion(1); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); - - _context.setFormat(format); - if (nullptr != sharedContext) { - _context.setShareContext(sharedContext); - } - _context.create(); - - _offscreenSurface.setFormat(_context.format()); - _offscreenSurface.create(); -} - -bool OffscreenGlContext::makeCurrent() { - return _context.makeCurrent(&_offscreenSurface); -} - -void OffscreenGlContext::doneCurrent() { - _context.doneCurrent(); -} - diff --git a/libraries/render-utils/src/OffscreenGlContext.h b/libraries/render-utils/src/OffscreenGlContext.h deleted file mode 100644 index c0d22b268f..0000000000 --- a/libraries/render-utils/src/OffscreenGlContext.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// OffscreenGlCanvas.h -// interface/src/renderer -// -// Created by Bradley Austin Davis on 2014/04/09. -// Copyright 2015 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 -// -#pragma once -#ifndef hifi_OffscreenGlContext_h -#define hifi_OffscreenGlContext_h - -#include -#include - -class OffscreenGlContext : public QObject { -public: - OffscreenGlContext(); - void create(QOpenGLContext * sharedContext = nullptr); - bool makeCurrent(); - void doneCurrent(); - QOpenGLContext * getContext() { - return &_context; - } - -protected: - QOpenGLContext _context; - QOffscreenSurface _offscreenSurface; -}; - -#endif // hifi_OffscreenGlCanvas_h diff --git a/libraries/shared/src/GLHelpers.cpp b/libraries/shared/src/GLHelpers.cpp new file mode 100644 index 0000000000..f2371e49bb --- /dev/null +++ b/libraries/shared/src/GLHelpers.cpp @@ -0,0 +1,16 @@ +#include "GLHelpers.h" + + +QSurfaceFormat getDefaultOpenGlSurfaceFormat() { + QSurfaceFormat format; + // Qt Quick may need a depth and stencil buffer. Always make sure these are available. + format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); + format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); + format.setVersion(4, 1); +#ifdef DEBUG + format.setOption(QSurfaceFormat::DebugContext); +#endif + // FIXME move to core as soon as possible + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + return format; +} diff --git a/libraries/shared/src/GLHelpers.h b/libraries/shared/src/GLHelpers.h new file mode 100644 index 0000000000..dc9f0f3140 --- /dev/null +++ b/libraries/shared/src/GLHelpers.h @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis 2015/05/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 +// + +#pragma once +#ifndef hifi_GLHelpers_h +#define hifi_GLHelpers_h + +#include + +// 16 bits of depth precision +#define DEFAULT_GL_DEPTH_BUFFER_BITS 16 +// 8 bits of stencil buffer (typically you really only need 1 bit for functionality +// but GL implementations usually just come with buffer sizes in multiples of 8) +#define DEFAULT_GL_STENCIL_BUFFER_BITS 8 + +QSurfaceFormat getDefaultOpenGlSurfaceFormat(); + +#endif diff --git a/libraries/ui/src/ErrorDialog.cpp b/libraries/ui/src/ErrorDialog.cpp new file mode 100644 index 0000000000..ab36ef8d36 --- /dev/null +++ b/libraries/ui/src/ErrorDialog.cpp @@ -0,0 +1,38 @@ +// +// ErrorDialog.cpp +// +// Created by David Rowe on 30 May 2015 +// Copyright 2015 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 "ErrorDialog.h" + +HIFI_QML_DEF(ErrorDialog) + +ErrorDialog::ErrorDialog(QQuickItem* parent) : OffscreenQmlDialog(parent) { +} + +ErrorDialog::~ErrorDialog() { +} + +QString ErrorDialog::text() const { + return _text; +} + +void ErrorDialog::setVisible(bool v) { + OffscreenQmlDialog::setVisible(v); +} + +void ErrorDialog::setText(const QString& arg) { + if (arg != _text) { + _text = arg; + emit textChanged(); + } +} + +void ErrorDialog::accept() { + OffscreenQmlDialog::accept(); +} diff --git a/libraries/ui/src/ErrorDialog.h b/libraries/ui/src/ErrorDialog.h new file mode 100644 index 0000000000..665090da1a --- /dev/null +++ b/libraries/ui/src/ErrorDialog.h @@ -0,0 +1,46 @@ +// +// ErrorDialog.h +// +// Created by David Rowe on 30 May 2015 +// Copyright 2015 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 +// + +#pragma once + +#ifndef hifi_ErrorDialog_h +#define hifi_ErrorDialog_h + +#include "OffscreenQmlDialog.h" + +class ErrorDialog : public OffscreenQmlDialog +{ + Q_OBJECT + HIFI_QML_DECL + +private: + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + +public: + ErrorDialog(QQuickItem* parent = 0); + virtual ~ErrorDialog(); + + QString text() const; + +public slots: + virtual void setVisible(bool v); + void setText(const QString& arg); + +signals: + void textChanged(); + +protected slots: + virtual void accept(); + +private: + QString _text; +}; + +#endif // hifi_ErrorDialog_h diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 449657ca04..d94cad20d2 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "ErrorDialog.h" #include "MessageDialog.h" @@ -130,6 +131,15 @@ void OffscreenUi::critical(const QString& title, const QString& text, static_cast(MessageDialog::Critical), buttons); } +void OffscreenUi::error(const QString& text) { + ErrorDialog* pDialog{ nullptr }; + ErrorDialog::show([&](QQmlContext* ctx, QObject* item) { + pDialog = item->findChild(); + pDialog->setText(text); + }); + pDialog->setEnabled(true); +} + OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {}; diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index d3567bbb5e..4d0044e775 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -115,6 +115,8 @@ public: static void critical(const QString& title, const QString& text, ButtonCallback callback = NO_OP_CALLBACK, QMessageBox::StandardButtons buttons = QMessageBox::Ok); + + static void error(const QString& text); // Interim dialog in new style }; #endif