diff --git a/CMakeLists.txt b/CMakeLists.txt index 347341efa0..e57e33e3b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,8 @@ if (WIN32) endif () message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH}) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory. # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables # TODO: Remove when building 64-bit. diff --git a/examples/blockWorld.js b/examples/blockWorld.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index 7dd2b5974f..146f9daca3 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -153,6 +153,26 @@ if (showScore) { var BULLET_VELOCITY = 10.0; +function entityCollisionWithEntity(entity1, entity2, collision) { + if (entity2 === targetID) { + score++; + if (showScore) { + Overlays.editOverlay(text, { text: "Score: " + score } ); + } + + // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! + Script.setTimeout(deleteBulletAndTarget, 500); + + // Turn the target and the bullet white + Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); + Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); + + // play the sound near the camera so the shooter can hear it + audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + Audio.playSound(targetHitSound, audioOptions); + } +} + function shootBullet(position, velocity, grenade) { var BULLET_SIZE = 0.10; var BULLET_LIFETIME = 10.0; @@ -178,6 +198,7 @@ function shootBullet(position, velocity, grenade) { ignoreCollisions: false, collisionsWillMove: true }); + Script.addEventHandler(bulletID, "collisionWithEntity", entityCollisionWithEntity); // Play firing sounds audioOptions.position = position; @@ -310,27 +331,6 @@ function makePlatform(gravity, scale, size) { } -function entityCollisionWithEntity(entity1, entity2, collision) { - if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) && - ((entity2.id == bulletID.id) || (entity2.id == targetID.id))) { - score++; - if (showScore) { - Overlays.editOverlay(text, { text: "Score: " + score } ); - } - - // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! - Script.setTimeout(deleteBulletAndTarget, 500); - - // Turn the target and the bullet white - Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); - Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); - - // play the sound near the camera so the shooter can hear it - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - Audio.playSound(targetHitSound, audioOptions); - } -} - function keyPressEvent(event) { // if our tools are off, then don't do anything if (event.text == "t") { @@ -505,7 +505,6 @@ function scriptEnding() { clearPose(); } -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); diff --git a/examples/example/entityCollisionExample.js b/examples/example/entityCollisionExample.js new file mode 100644 index 0000000000..de50d52753 --- /dev/null +++ b/examples/example/entityCollisionExample.js @@ -0,0 +1,53 @@ +// +// entityCollisionExample.js +// examples +// +// Created by Howard Stearns on 5/25/15. +// Copyright 2015 High Fidelity, Inc. +// +// This is an example script that demonstrates use of the per-entity event handlers. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function someCollisionFunction(entityA, entityB, collision) { + print("collision: " + JSON.stringify({a: entityA, b: entityB, c: collision})); +} + +var position = Vec3.sum(MyAvatar.position, Quat.getFront(MyAvatar.orientation)); +var properties = { + type: "Box", + position: position, + collisionsWillMove: true, + color: { red: 200, green: 0, blue: 0 } +}; +var collider = Entities.addEntity(properties); +var armed = false; +function togglePrinting() { + print('togglePrinting from ' + armed + ' on ' + collider); + if (armed) { + Script.removeEventHandler(collider, "collisionWithEntity", someCollisionFunction); + } else { + Script.addEventHandler(collider, "collisionWithEntity", someCollisionFunction); + } + armed = !armed; + print("Red box " + (armed ? "will" : "will not") + " print on collision."); +} +togglePrinting(); + +properties.position.y += 0.2; +properties.color.blue += 200; +// A handy target for the collider to hit. +var target = Entities.addEntity(properties); + +properties.position.y += 0.2; +properties.color.green += 200; +var button = Entities.addEntity(properties); +Script.addEventHandler(button, "clickReleaseOnEntity", togglePrinting); + +Script.scriptEnding.connect(function () { + Entities.deleteEntity(collider); + Entities.deleteEntity(target); + Entities.deleteEntity(button); +}); diff --git a/examples/example/games/billiards.js b/examples/example/games/billiards.js index 5e08322c77..25ff5e7eae 100644 --- a/examples/example/games/billiards.js +++ b/examples/example/games/billiards.js @@ -33,8 +33,8 @@ var cuePosition; var startStroke = 0; // Sounds to use -hitSounds = []; -hitSounds.push(SoundCache.getSound(HIFI_PUBLIC_BUCKET + "Collisions-ballhitsandcatches/billiards/collision1.wav")); +var hitSound = HIFI_PUBLIC_BUCKET + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav"; +SoundCache.getSound(hitSound); HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); @@ -127,6 +127,7 @@ function makeBalls(pos) { ignoreCollisions: false, damping: 0.50, shapeType: "sphere", + collisionSoundURL: hitSound, collisionsWillMove: true })); ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; ballNumber++; @@ -225,26 +226,11 @@ function update(deltaTime) { } } -function entityCollisionWithEntity(entity1, entity2, collision) { - /* - NOT WORKING YET - if ((entity1.id == cueBall.id) || (entity2.id == cueBall.id)) { - print("Cue ball collision!"); - //audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - //Audio.playSound(hitSounds[0], { position: Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())) }); - } - - else if (isObjectBall(entity1.id) || isObjectBall(entity2.id)) { - print("Object ball collision"); - } */ -} - tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))); makeTable(tableCenter); makeBalls(tableCenter); -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(cleanup); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); diff --git a/examples/example/games/spaceInvadersExample.js b/examples/example/games/spaceInvadersExample.js index 819cf6b774..08ad56c04d 100644 --- a/examples/example/games/spaceInvadersExample.js +++ b/examples/example/games/spaceInvadersExample.js @@ -21,6 +21,16 @@ var gameOver = false; var invaderStepsPerCycle = 120; // the number of update steps it takes then invaders to move one column to the right var invaderStepOfCycle = 0; // current iteration in the cycle var invaderMoveDirection = 1; // 1 for moving to right, -1 for moving to left +var LEFT = ","; +var RIGHT = "."; +var FIRE = "SPACE"; +var QUIT = "q"; + +print("Use:"); +print(LEFT + " to move left"); +print(RIGHT + " to move right"); +print(FIRE + " to fire"); +print(QUIT + " to quit"); // game length... var itemLifetimes = 60; // 1 minute @@ -65,8 +75,8 @@ var myShipProperties; // create the rows of space invaders var invaders = new Array(); -var numberOfRows = 5; -var invadersPerRow = 8; +var numberOfRows = 3 // FIXME 5; +var invadersPerRow = 3 // FIXME 8; var emptyColumns = 2; // number of invader width columns not filled with invaders var invadersBottomCorner = { x: gameAt.x, y: middleY , z: gameAt.z }; var rowHeight = ((gameAt.y + gameSize.y) - invadersBottomCorner.y) / numberOfRows; @@ -80,7 +90,6 @@ var stepsToGround = (middleY - gameAt.y) / yPerStep; var maxInvaderRowOffset=stepsToGround; // missile related items -var missileFired = false; var myMissile; // sounds @@ -174,6 +183,7 @@ function initializeInvaders() { invaderPosition = getInvaderPosition(row, column); invaders[row][column] = Entities.addEntity({ type: "Model", + shapeType: "box", position: invaderPosition, velocity: { x: 0, y: 0, z: 0 }, gravity: { x: 0, y: 0, z: 0 }, @@ -181,6 +191,7 @@ function initializeInvaders() { dimensions: { x: invaderSize * 2, y: invaderSize * 2, z: invaderSize * 2 }, color: { red: 255, green: 0, blue: 0 }, modelURL: invaderModels[row].modelURL, + collisionsWillMove: true, lifetime: itemLifetimes }); } @@ -264,17 +275,17 @@ Script.update.connect(update); function cleanupGame() { print("cleaning up game..."); Entities.deleteEntity(myShip); - print("cleanupGame() ... Entities.deleteEntity(myShip)... myShip.id="+myShip.id); + print("cleanupGame() ... Entities.deleteEntity(myShip)... myShip="+myShip); for (var row = 0; row < numberOfRows; row++) { for (var column = 0; column < invadersPerRow; column++) { Entities.deleteEntity(invaders[row][column]); - print("cleanupGame() ... Entities.deleteEntity(invaders[row][column])... invaders[row][column].id=" - +invaders[row][column].id); + print("cleanupGame() ... Entities.deleteEntity(invaders[row][column])... invaders[row][column]=" + +invaders[row][column]); } } // clean up our missile - if (missileFired) { + if (myMissile) { Entities.deleteEntity(myMissile); } @@ -293,15 +304,23 @@ function moveShipTo(position) { Entities.editEntity(myShip, { position: position }); } +function entityCollisionWithEntity(entityA, entityB, collision) { + print("entityCollisionWithEntity() a="+entityA + " b=" + entityB); + Vec3.print('entityCollisionWithEntity() penetration=', collision.penetration); + Vec3.print('entityCollisionWithEntity() contactPoint=', collision.contactPoint); + + deleteIfInvader(entityB); +} + function fireMissile() { // we only allow one missile at a time... var canFire = false; // If we've fired a missile, then check to see if it's still alive - if (missileFired) { + if (myMissile) { var missileProperties = Entities.getEntityProperties(myMissile); - if (!missileProperties) { + if (!missileProperties || (missileProperties.type === 'Unknown')) { print("canFire = true"); canFire = true; } @@ -322,11 +341,12 @@ function fireMissile() { velocity: { x: 0, y: 5, z: 0}, gravity: { x: 0, y: 0, z: 0 }, damping: 0, - dimensions: { x: missileSize * 2, y: missileSize * 2, z: missileSize * 2 }, + collisionsWillMove: true, + dimensions: { x: missileSize, y: missileSize, z: missileSize }, color: { red: 0, green: 0, blue: 255 }, lifetime: 5 }); - + Script.addEventHandler(myMissile, "collisionWithEntity", entityCollisionWithEntity); var options = {} if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, @@ -335,30 +355,30 @@ function fireMissile() { } else { options.position = missilePosition; } - + Audio.playSound(shootSound, options); - missileFired = true; } } function keyPressEvent(key) { //print("keyPressEvent key.text="+key.text); - if (key.text == ",") { + + if (key.text == LEFT) { myShipProperties.position.x -= 0.1; if (myShipProperties.position.x < gameAt.x) { myShipProperties.position.x = gameAt.x; } moveShipTo(myShipProperties.position); - } else if (key.text == ".") { + } else if (key.text == RIGHT) { myShipProperties.position.x += 0.1; if (myShipProperties.position.x > gameAt.x + gameSize.x) { myShipProperties.position.x = gameAt.x + gameSize.x; } moveShipTo(myShipProperties.position); - } else if (key.text == "f") { + } else if (key.text == FIRE) { fireMissile(); - } else if (key.text == "q") { + } else if (key.text == QUIT) { endGame(); } } @@ -370,7 +390,7 @@ Controller.captureKeyEvents({text: " "}); function deleteIfInvader(possibleInvaderEntity) { for (var row = 0; row < numberOfRows; row++) { for (var column = 0; column < invadersPerRow; column++) { - if (invaders[row][column].id && invaders[row][column].id == possibleInvaderEntity.id) { + if (invaders[row][column] == possibleInvaderEntity) { Entities.deleteEntity(possibleInvaderEntity); Entities.deleteEntity(myMissile); @@ -390,20 +410,6 @@ function deleteIfInvader(possibleInvaderEntity) { } } -function entityCollisionWithEntity(entityA, entityB, collision) { - print("entityCollisionWithEntity() a.id="+entityA.id + " b.id=" + entityB.id); - Vec3.print('entityCollisionWithEntity() penetration=', collision.penetration); - Vec3.print('entityCollisionWithEntity() contactPoint=', collision.contactPoint); - if (missileFired) { - if (myMissile.id == entityA.id) { - deleteIfInvader(entityB); - } else if (myMissile.id == entityB.id) { - deleteIfInvader(entityA); - } - } -} -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); - // initialize the game... initializeMyShip(); diff --git a/examples/example/globalCollisionsExample.js b/examples/example/globalCollisionsExample.js index 5813cb2472..624ad43219 100644 --- a/examples/example/globalCollisionsExample.js +++ b/examples/example/globalCollisionsExample.js @@ -12,17 +12,6 @@ // -print("hello..."); - - -function entityCollisionWithEntity(entityA, entityB, collision) { - print("entityCollisionWithParticle().."); - print(" entityA.getID()=" + entityA.id); - print(" entityB.getID()=" + entityB.id); - Vec3.print('penetration=', collision.penetration); - Vec3.print('contactPoint=', collision.contactPoint); -} - -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); - -print("here... hello..."); +print("The global collision event is obsolete. Please instead use:"); +print(" the collisionSoundURL property on entities, or"); +print(" entityCollisionExample.js"); diff --git a/examples/pointer.js b/examples/pointer.js index cdfb93f2d3..eebe4ec5be 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -1,9 +1,88 @@ +// pointer.js +// examples +// +// Created by Eric Levin on May 26, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Provides a pointer with option to draw on surfaces +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + var lineEntityID = null; var lineIsRezzed = false; var altHeld = false; var lineCreated = false; +var position, positionOffset, prevPosition; +var nearLinePosition; +var strokes = []; +var STROKE_ADJUST = 0.005; +var DISTANCE_DRAW_THRESHOLD = .02; +var drawDistance = 0; -function nearLinePoint(targetPosition) { +var LINE_WIDTH = 20; + +var userCanPoint = false; +var userCanDraw = false; + +var BUTTON_SIZE = 32; +var PADDING = 3; + +var buttonOffColor = { + red: 250, + green: 10, + blue: 10 +}; +var buttonOnColor = { + red: 10, + green: 200, + blue: 100 +}; + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var screenSize = Controller.getViewportDimensions(); + +var drawButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE + PADDING * 2, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/pencil.png?v2", + color: buttonOffColor, + alpha: 1 +}); + +var pointerButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/laser.png", + color: buttonOffColor, + alpha: 1 +}) + + + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2.0, Quat.getFront(Camera.getOrientation()))); +center.y += 0.5; +var whiteBoard = Entities.addEntity({ + type: "Box", + position: center, + dimensions: { + x: 1, + y: 1, + z: .001 + }, + color: { + red: 255, + green: 255, + blue: 255 + } +}); + +function calculateNearLinePosition(targetPosition) { var handPosition = MyAvatar.getRightPalmPosition(); var along = Vec3.subtract(targetPosition, handPosition); along = Vec3.normalize(along); @@ -22,30 +101,39 @@ function removeLine() { function createOrUpdateLine(event) { + if (!userCanPoint) { + return; + } var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking var props = Entities.getEntityProperties(intersection.entityID); if (intersection.intersects) { - var dim = Vec3.subtract(intersection.intersection, nearLinePoint(intersection.intersection)); + startPosition = intersection.intersection; + var subtractVec = Vec3.multiply(Vec3.normalize(pickRay.direction), STROKE_ADJUST); + startPosition = Vec3.subtract(startPosition, subtractVec); + nearLinePosition = calculateNearLinePosition(intersection.intersection); + positionOffset = Vec3.subtract(startPosition, nearLinePosition); if (lineIsRezzed) { Entities.editEntity(lineEntityID, { - position: nearLinePoint(intersection.intersection), - dimensions: dim, - lifetime: 15 + props.lifespan // renew lifetime + position: nearLinePosition, + dimensions: positionOffset, }); + if (userCanDraw) { + draw(); + } } else { lineIsRezzed = true; + prevPosition = startPosition; lineEntityID = Entities.addEntity({ type: "Line", - position: nearLinePoint(intersection.intersection), - dimensions: dim, + position: nearLinePosition, + dimensions: positionOffset, color: { red: 255, green: 255, blue: 255 }, - lifetime: 15 // if someone crashes while pointing, don't leave the line there forever. }); } } else { @@ -53,8 +141,69 @@ function createOrUpdateLine(event) { } } +function draw() { + + //We only want to draw line if distance between starting and previous point is large enough + drawDistance = Vec3.distance(startPosition, prevPosition); + if (drawDistance < DISTANCE_DRAW_THRESHOLD) { + return; + } + + var offset = Vec3.subtract(startPosition, prevPosition); + strokes.push(Entities.addEntity({ + type: "Line", + position: prevPosition, + dimensions: offset, + color: { + red: 200, + green: 40, + blue: 200 + }, + lineWidth: LINE_WIDTH + })); + prevPosition = startPosition; +} function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == drawButton) { + userCanDraw = !userCanDraw; + if (userCanDraw === true) { + Overlays.editOverlay(drawButton, { + color: buttonOnColor + }); + } else { + Overlays.editOverlay(drawButton, { + color: buttonOffColor + }); + } + } + + if (clickedOverlay == pointerButton) { + userCanPoint = !userCanPoint; + if (userCanPoint === true) { + Overlays.editOverlay(pointerButton, { + color: buttonOnColor + }); + if (userCanDraw === true) { + + Overlays.editOverlay(drawButton, { + color: buttonOnColor + }); + } + } else { + Overlays.editOverlay(pointerButton, { + color: buttonOffColor + }); + Overlays.editOverlay(drawButton, { + color: buttonOffColor + }); + } + } + if (!event.isLeftButton || altHeld) { return; } @@ -64,11 +213,13 @@ function mousePressEvent(event) { } + function mouseMoveEvent(event) { createOrUpdateLine(event); } + function mouseReleaseEvent(event) { if (!lineCreated) { return; @@ -91,7 +242,18 @@ function keyReleaseEvent(event) { } +function cleanup() { + Entities.deleteEntity(whiteBoard); + for (var i = 0; i < strokes.length; i++) { + Entities.deleteEntity(strokes[i]); + } + Overlays.deleteOverlay(drawButton); + Overlays.deleteOverlay(pointerButton); +} + + +Script.scriptEnding.connect(cleanup); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7de903edf2..8f1ce13851 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2175,9 +2175,6 @@ void Application::init() { auto entityScriptingInterface = DependencyManager::get(); - connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, - entityScriptingInterface.data(), &EntityScriptingInterface::entityCollisionWithEntity); - // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, &_entities, &EntityTreeRenderer::entityCollisionWithEntity); diff --git a/interface/src/audio/AudioToolBox.cpp b/interface/src/audio/AudioToolBox.cpp index 85b8b19788..68328e151e 100644 --- a/interface/src/audio/AudioToolBox.cpp +++ b/interface/src/audio/AudioToolBox.cpp @@ -40,13 +40,13 @@ void AudioToolBox::render(int x, int y, int padding, bool boxed) { glEnable(GL_TEXTURE_2D); if (!_micTexture) { - _micTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/mic.svg"); + _micTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic.svg"); } if (!_muteTexture) { - _muteTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg"); + _muteTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg"); } if (_boxTexture) { - _boxTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg"); + _boxTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg"); } auto audioIO = DependencyManager::get(); diff --git a/interface/src/devices/CameraToolBox.cpp b/interface/src/devices/CameraToolBox.cpp index a1e00d7052..27cee5185b 100644 --- a/interface/src/devices/CameraToolBox.cpp +++ b/interface/src/devices/CameraToolBox.cpp @@ -76,10 +76,10 @@ void CameraToolBox::render(int x, int y, bool boxed) { glEnable(GL_TEXTURE_2D); if (!_enabledTexture) { - _enabledTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/face.svg"); + _enabledTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face.svg"); } if (!_mutedTexture) { - _mutedTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg"); + _mutedTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg"); } const int MUTE_ICON_SIZE = 24; diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index fba37736e3..27e15cea0e 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -141,13 +141,6 @@ static const float STARTING_DDE_MESSAGE_TIME = 0.033f; static const float DEFAULT_DDE_EYE_CLOSING_THRESHOLD = 0.8f; static const int CALIBRATION_SAMPLES = 150; -#ifdef WIN32 -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeBlinks' will be default initialized -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_filteredEyeBlinks' will be default initialized -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeCoefficients' will be default initialized -#pragma warning(disable:4351) -#endif - DdeFaceTracker::DdeFaceTracker() : DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT) { @@ -214,10 +207,6 @@ DdeFaceTracker::~DdeFaceTracker() { } } -#ifdef WIN32 -#pragma warning(default:4351) -#endif - void DdeFaceTracker::init() { FaceTracker::init(); setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) && !_isMuted); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 678fa096c3..0778e5be6b 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -423,8 +423,8 @@ void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float }); if (!_crosshairTexture) { - _crosshairTexture = DependencyManager::get()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); + _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + + "images/sixense-reticle.png"); } //draw the mouse pointer @@ -564,8 +564,7 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, void ApplicationOverlay::renderPointers() { //lazily load crosshair texture if (_crosshairTexture == 0) { - _crosshairTexture = DependencyManager::get()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); + _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); } glEnable(GL_TEXTURE_2D); diff --git a/interface/src/ui/RearMirrorTools.cpp b/interface/src/ui/RearMirrorTools.cpp index 94dd614edf..33f3e1b487 100644 --- a/interface/src/ui/RearMirrorTools.cpp +++ b/interface/src/ui/RearMirrorTools.cpp @@ -34,11 +34,10 @@ RearMirrorTools::RearMirrorTools(QRect& bounds) : _windowed(false), _fullScreen(false) { - auto textureCache = DependencyManager::get(); - _closeTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/close.svg"); + _closeTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/close.svg"); - _zoomHeadTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/plus.svg"); - _zoomBodyTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/minus.svg"); + _zoomHeadTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/plus.svg"); + _zoomBodyTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/minus.svg"); _shrinkIconRect = QRect(ICON_PADDING, ICON_PADDING, ICON_SIZE, ICON_SIZE); _closeIconRect = QRect(_bounds.left() + ICON_PADDING, _bounds.top() + ICON_PADDING, ICON_SIZE, ICON_SIZE); diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 54156d5d2b..e709bbd9fc 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -24,6 +24,7 @@ TextOverlay::TextOverlay() : _topMargin(DEFAULT_MARGIN), _fontSize(DEFAULT_FONTSIZE) { + _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); } TextOverlay::TextOverlay(const TextOverlay* textOverlay) : @@ -35,6 +36,7 @@ TextOverlay::TextOverlay(const TextOverlay* textOverlay) : _topMargin(textOverlay->_topMargin), _fontSize(textOverlay->_fontSize) { + _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); } TextOverlay::~TextOverlay() { diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index 5b77be0cac..5a715ebfdf 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -60,7 +60,7 @@ public: private: - TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); + TextRenderer* _textRenderer = nullptr; QString _text; xColor _backgroundColor; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 057749c25f..8a67bb99f2 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -20,52 +20,41 @@ #include "RenderableTextEntityItem.h" #include "GLMHelpers.h" - EntityItemPointer RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return EntityItemPointer(new RenderableTextEntityItem(entityID, properties)); } void RenderableTextEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableTextEntityItem::render"); - assert(getType() == EntityTypes::Text); - glm::vec3 position = getPosition(); + Q_ASSERT(getType() == EntityTypes::Text); + + static const float SLIGHTLY_BEHIND = -0.005f; + glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f); + glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f); glm::vec3 dimensions = getDimensions(); - glm::vec3 halfDimensions = dimensions / 2.0f; - glm::quat rotation = getRotation(); - float leftMargin = 0.1f; - float topMargin = 0.1f; - - //qCDebug(entitytree) << "RenderableTextEntityItem::render() id:" << getEntityItemID() << "text:" << getText(); - - glPushMatrix(); - { - 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); - - float alpha = 1.0f; //getBackgroundAlpha(); - static const float SLIGHTLY_BEHIND = -0.005f; - - glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND); - glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND); - - // TODO: Determine if we want these entities to have the deferred lighting effect? I think we do, so that the color - // used for a sphere, or box have the same look as those used on a text entity. - //DependencyManager::get()->bindSimpleProgram(); - DependencyManager::get()->renderQuad(topLeft, bottomRight, glm::vec4(toGlm(getBackgroundColorX()), alpha)); - //DependencyManager::get()->releaseSimpleProgram(); - - glTranslatef(-(halfDimensions.x - leftMargin), halfDimensions.y - topMargin, 0.0f); - glm::vec4 textColor(toGlm(getTextColorX()), alpha); - // this is a ratio determined through experimentation - const float scaleFactor = 0.08f * _lineHeight; - glScalef(scaleFactor, -scaleFactor, scaleFactor); - glm::vec2 bounds(dimensions.x / scaleFactor, dimensions.y / scaleFactor); - _textRenderer->draw(0, 0, _text, textColor, bounds); - } - glPopMatrix(); + + Transform transformToTopLeft = getTransformToCenter(); + transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left + transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed + + // Batch render calls + Q_ASSERT(args->_batch); + gpu::Batch& batch = *args->_batch; + batch.setModelTransform(transformToTopLeft); + + // Render background + glm::vec3 minCorner = glm::vec3(0.0f, -dimensions.y, SLIGHTLY_BEHIND); + glm::vec3 maxCorner = glm::vec3(dimensions.x, 0.0f, SLIGHTLY_BEHIND); + DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, backgroundColor); + + float scale = _lineHeight / _textRenderer->getRowHeight(); + transformToTopLeft.setScale(scale); // Scale to have the correct line height + batch.setModelTransform(transformToTopLeft); + + float leftMargin = 0.5f * _lineHeight, topMargin = 0.5f * _lineHeight; + glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, dimensions.y - 2.0f * topMargin); + _textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, _text, textColor, bounds / scale); } - diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index eb2cfc16f1..5db22f3045 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -13,7 +13,7 @@ #define hifi_RenderableTextEntityItem_h #include -#include +#include const int FIXED_FONT_POINT_SIZE = 40; @@ -30,7 +30,7 @@ public: virtual bool canRenderInScene() { return false; } // we don't yet play well with others private: - TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f); + TextRenderer3D* _textRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f); }; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6ad2bed291..c2c0e5603f 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -318,15 +318,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return 0; } - // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. - glm::vec3 savePosition = getPosition(); - glm::quat saveRotation = getRotation(); - // glm::vec3 saveVelocity = _velocity; - // glm::vec3 saveAngularVelocity = _angularVelocity; - // glm::vec3 saveGravity = _gravity; - // glm::vec3 saveAcceleration = _acceleration; - - // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] @@ -337,299 +328,308 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef const int MINIMUM_HEADER_BYTES = 27; int bytesRead = 0; - if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) { + if (bytesLeftToRead < MINIMUM_HEADER_BYTES) { + return 0; + } - int originalLength = bytesLeftToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. + glm::vec3 savePosition = getPosition(); + glm::quat saveRotation = getRotation(); + glm::vec3 saveVelocity = _velocity; + glm::vec3 saveAngularVelocity = _angularVelocity; - int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; + int originalLength = bytesLeftToRead; + QByteArray originalDataBuffer((const char*)data, originalLength); - const unsigned char* dataAt = data; + int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; - // id - QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size - _id = QUuid::fromRfc4122(encodedID); - dataAt += encodedID.size(); - bytesRead += encodedID.size(); - - // type - QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded typeCoder = encodedType; - encodedType = typeCoder; // determine true length - dataAt += encodedType.size(); - bytesRead += encodedType.size(); - quint32 type = typeCoder; - _type = (EntityTypes::EntityType)type; + const unsigned char* dataAt = data; - bool overwriteLocalData = true; // assume the new content overwrites our local data + // id + QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size + _id = QUuid::fromRfc4122(encodedID); + dataAt += encodedID.size(); + bytesRead += encodedID.size(); + + // type + QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded typeCoder = encodedType; + encodedType = typeCoder; // determine true length + dataAt += encodedType.size(); + bytesRead += encodedType.size(); + quint32 type = typeCoder; + _type = (EntityTypes::EntityType)type; - // _created - quint64 createdFromBuffer = 0; - memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); - dataAt += sizeof(createdFromBuffer); - bytesRead += sizeof(createdFromBuffer); + bool overwriteLocalData = true; // assume the new content overwrites our local data - quint64 now = usecTimestampNow(); - if (_created == UNKNOWN_CREATED_TIME) { - // we don't yet have a _created timestamp, so we accept this one - createdFromBuffer -= clockSkew; - if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { - createdFromBuffer = now; - } - _created = createdFromBuffer; + // _created + quint64 createdFromBuffer = 0; + memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); + dataAt += sizeof(createdFromBuffer); + bytesRead += sizeof(createdFromBuffer); + + quint64 now = usecTimestampNow(); + if (_created == UNKNOWN_CREATED_TIME) { + // we don't yet have a _created timestamp, so we accept this one + createdFromBuffer -= clockSkew; + if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { + createdFromBuffer = now; } + _created = createdFromBuffer; + } + #ifdef WANT_DEBUG + quint64 lastEdited = getLastEdited(); + float editedAgo = getEditedAgo(); + QString agoAsString = formatSecondsElapsed(editedAgo); + QString ageAsString = formatSecondsElapsed(getAge()); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; + qCDebug(entities) << "------------------------------------------"; + debugDump(); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << " _created =" << _created; + qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; + qCDebug(entities) << " lastEdited =" << lastEdited; + qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; + #endif + + quint64 lastEditedFromBuffer = 0; + quint64 lastEditedFromBufferAdjusted = 0; + + // TODO: we could make this encoded as a delta from _created + // _lastEdited + memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); + dataAt += sizeof(lastEditedFromBuffer); + bytesRead += sizeof(lastEditedFromBuffer); + lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; + if (lastEditedFromBufferAdjusted > now) { + lastEditedFromBufferAdjusted = now; + } + + bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); + + #ifdef WANT_DEBUG + qCDebug(entities) << "data from server **************** "; + qCDebug(entities) << " entityItemID:" << getEntityItemID(); + qCDebug(entities) << " now:" << now; + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); + qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); + qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); + qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + #endif + + bool ignoreServerPacket = false; // assume we'll use this server packet + + // If this packet is from the same server edit as the last packet we accepted from the server + // we probably want to use it. + if (fromSameServerEdit) { + // If this is from the same sever packet, then check against any local changes since we got + // the most recent packet from this server time + if (_lastEdited > _lastEditedFromRemote) { + ignoreServerPacket = true; + } + } else { + // If this isn't from the same sever packet, then honor our skew adjusted times... + // If we've changed our local tree more recently than the new data from this packet + // then we will not be changing our values, instead we just read and skip the data + if (_lastEdited > lastEditedFromBufferAdjusted) { + ignoreServerPacket = true; + } + } + + if (ignoreServerPacket) { + overwriteLocalData = false; #ifdef WANT_DEBUG - quint64 lastEdited = getLastEdited(); - float editedAgo = getEditedAgo(); - QString agoAsString = formatSecondsElapsed(editedAgo); - QString ageAsString = formatSecondsElapsed(getAge()); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; - qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "IGNORING old data from server!!! ****************"; debugDump(); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << " _created =" << _created; - qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; - qCDebug(entities) << " lastEdited =" << lastEdited; - qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; #endif - - quint64 lastEditedFromBuffer = 0; - quint64 lastEditedFromBufferAdjusted = 0; - - // TODO: we could make this encoded as a delta from _created - // _lastEdited - memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); - dataAt += sizeof(lastEditedFromBuffer); - bytesRead += sizeof(lastEditedFromBuffer); - lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; - if (lastEditedFromBufferAdjusted > now) { - lastEditedFromBufferAdjusted = now; - } - - bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); - + } else { #ifdef WANT_DEBUG - qCDebug(entities) << "data from server **************** "; - qCDebug(entities) << " entityItemID:" << getEntityItemID(); - qCDebug(entities) << " now:" << now; - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); - qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); - qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); - qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + qCDebug(entities) << "USING NEW data from server!!! ****************"; + debugDump(); #endif - bool ignoreServerPacket = false; // assume we'll use this server packet - - // If this packet is from the same server edit as the last packet we accepted from the server - // we probably want to use it. - if (fromSameServerEdit) { - // If this is from the same sever packet, then check against any local changes since we got - // the most recent packet from this server time - if (_lastEdited > _lastEditedFromRemote) { - ignoreServerPacket = true; - } - } else { - // If this isn't from the same sever packet, then honor our skew adjusted times... - // If we've changed our local tree more recently than the new data from this packet - // then we will not be changing our values, instead we just read and skip the data - if (_lastEdited > lastEditedFromBufferAdjusted) { - ignoreServerPacket = true; - } - } + // don't allow _lastEdited to be in the future + _lastEdited = lastEditedFromBufferAdjusted; + _lastEditedFromRemote = now; + _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - if (ignoreServerPacket) { - overwriteLocalData = false; - #ifdef WANT_DEBUG - qCDebug(entities) << "IGNORING old data from server!!! ****************"; - debugDump(); - #endif - } else { + // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed + // the properties out of the bitstream (see below)) + somethingChangedNotification(); // notify derived classes that something has changed + } - #ifdef WANT_DEBUG - qCDebug(entities) << "USING NEW data from server!!! ****************"; - debugDump(); - #endif - - // don't allow _lastEdited to be in the future - _lastEdited = lastEditedFromBufferAdjusted; - _lastEditedFromRemote = now; - _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - - // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed - // the properties out of the bitstream (see below)) - somethingChangedNotification(); // notify derived classes that something has changed - } - - // last updated is stored as ByteCountCoded delta from lastEdited - QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded updateDeltaCoder = encodedUpdateDelta; - quint64 updateDelta = updateDeltaCoder; + // last updated is stored as ByteCountCoded delta from lastEdited + QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded updateDeltaCoder = encodedUpdateDelta; + quint64 updateDelta = updateDeltaCoder; + if (overwriteLocalData) { + _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + #ifdef WANT_DEBUG + qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + #endif + } + encodedUpdateDelta = updateDeltaCoder; // determine true length + dataAt += encodedUpdateDelta.size(); + bytesRead += encodedUpdateDelta.size(); + + // Newer bitstreams will have a last simulated and a last updated value + 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) { - _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that #ifdef WANT_DEBUG - qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); #endif } - encodedUpdateDelta = updateDeltaCoder; // determine true length - dataAt += encodedUpdateDelta.size(); - bytesRead += encodedUpdateDelta.size(); - - // Newer bitstreams will have a last simulated and a last updated value - 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; + encodedSimulatedDelta = simulatedDeltaCoder; // determine true length + dataAt += encodedSimulatedDelta.size(); + bytesRead += encodedSimulatedDelta.size(); + } + + #ifdef WANT_DEBUG + if (overwriteLocalData) { + qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); + qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); + } + #endif + + + // Property Flags + QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size + EntityPropertyFlags propertyFlags = encodedPropertyFlags; + dataAt += propertyFlags.getEncodedLength(); + bytesRead += propertyFlags.getEncodedLength(); + bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + } else { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); + } + + // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS + if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { + if (propertyFlags.getHasProperty(PROP_RADIUS)) { + float fromBuffer; + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); + dataAt += sizeof(fromBuffer); + bytesRead += sizeof(fromBuffer); if (overwriteLocalData) { - _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that - #ifdef WANT_DEBUG - qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); - qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - #endif + setRadius(fromBuffer); } - encodedSimulatedDelta = simulatedDeltaCoder; // determine true length - dataAt += encodedSimulatedDelta.size(); - bytesRead += encodedSimulatedDelta.size(); } - - #ifdef WANT_DEBUG - if (overwriteLocalData) { - qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); - qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); - } - #endif - - - // Property Flags - QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size - EntityPropertyFlags propertyFlags = encodedPropertyFlags; - dataAt += propertyFlags.getEncodedLength(); - bytesRead += propertyFlags.getEncodedLength(); - bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + } else { if (useMeters) { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); } else { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); - } - - // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { - if (propertyFlags.getHasProperty(PROP_RADIUS)) { - float fromBuffer; - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); - dataAt += sizeof(fromBuffer); - bytesRead += sizeof(fromBuffer); - if (overwriteLocalData) { - setRadius(fromBuffer); - } - } - } else { - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); - } else { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); - } - } - - READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); - READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); - } else { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); - } - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); - } - - READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); - READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); - READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); - READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); - } else { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); - } - READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); - READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); - READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); - READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); - READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); - READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); - } - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); - READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); - bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); - - //////////////////////////////////// - // WARNING: Do not add stream content here after the subclass. Always add it before the subclass - // - // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover - // by doing this parsing here... but it's not likely going to fully recover the content. - // - // TODO: Remove this conde once we've sufficiently migrated content past this damaged version - if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { - // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more - // closely match where the entities should be if they'd stepped forward in time to "now". The server - // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore - // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and - // 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); - if (skipTimeForward > 0.0f) { - #ifdef WANT_DEBUG - qCDebug(entities) << "skipTimeForward:" << skipTimeForward; - #endif - - // we want to extrapolate the motion forward to compensate for packet travel time, but - // we don't want the side effect of flag setting. - simulateKinematicMotion(skipTimeForward, false); - } - _lastSimulated = now; + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); } } + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); + } else { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); + } + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + } + + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + } else { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); + } + READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); + READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); + READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); + READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); + READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + // we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true + // before we try to READ_ENTITY_PROPERTY it + bool temp = overwriteLocalData; + overwriteLocalData = true; + READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); + overwriteLocalData = temp; + } + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); + READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); + + //////////////////////////////////// + // WARNING: Do not add stream content here after the subclass. Always add it before the subclass + // + // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover + // by doing this parsing here... but it's not likely going to fully recover the content. + // + // TODO: Remove this conde once we've sufficiently migrated content past this damaged version + if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { + // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more + // closely match where the entities should be if they'd stepped forward in time to "now". The server + // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore + // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and + // 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); + if (skipTimeForward > 0.0f) { + #ifdef WANT_DEBUG + qCDebug(entities) << "skipTimeForward:" << skipTimeForward; + #endif + + // we want to extrapolate the motion forward to compensate for packet travel time, but + // 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 (_simulatorID == myNodeID && !_simulatorID.isNull()) { - // the packet that produced this bitstream originally came from physics simulations performed by - // this node, so our version has to be newer than what the packet contained. + 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; - // _gravity = saveGravity; - // _acceleration = saveAcceleration; + _velocity = saveVelocity; + _angularVelocity = saveAngularVelocity; + _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); } return bytesRead; @@ -949,40 +949,25 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); if (somethingChanged) { - somethingChangedNotification(); // notify derived classes that something has changed uint64_t now = usecTimestampNow(); #ifdef WANT_DEBUG int elapsed = now - getLastEdited(); qCDebug(entities) << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); #endif - if (_created != UNKNOWN_CREATED_TIME) { - setLastEdited(now); + setLastEdited(now); + somethingChangedNotification(); // notify derived classes that something has changed + if (_created == UNKNOWN_CREATED_TIME) { + _created = now; } if (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES)) { - // TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases. + // anything that sets the transform or velocity must update _lastSimulated which is used + // for kinematic extrapolation (e.g. we want to extrapolate forward from this moment + // when position and/or velocity was changed). _lastSimulated = now; } } - // timestamps - quint64 timestamp = properties.getCreated(); - if (_created == UNKNOWN_CREATED_TIME && timestamp != UNKNOWN_CREATED_TIME) { - quint64 now = usecTimestampNow(); - if (timestamp > now) { - timestamp = now; - } - _created = timestamp; - - timestamp = properties.getLastEdited(); - if (timestamp > now) { - timestamp = now; - } else if (timestamp < _created) { - timestamp = _created; - } - _lastEdited = timestamp; - } - return somethingChanged; } @@ -1020,6 +1005,13 @@ void EntityItem::setTranformToCenter(const Transform& transform) { setTransform(copy); } +void EntityItem::setDimensions(const glm::vec3& value) { + if (value.x == 0.0f || value.y == 0.0f || value.z == 0.0f) { + return; + } + _transform.setScale(value); +} + /// The maximum bounding cube for the entity, independent of it's rotation. /// This accounts for the registration point (upon which rotation occurs around). /// diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4e299086e5..5cbd29cfa8 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -206,7 +206,7 @@ public: /// Dimensions in meters (0.0 - TREE_SCALE) inline const glm::vec3& getDimensions() const { return _transform.getScale(); } - inline virtual void setDimensions(const glm::vec3& value) { _transform.setScale(glm::abs(value)); } + virtual void setDimensions(const glm::vec3& value); float getGlowLevel() const { return _glowLevel; } diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index e27c5ec02e..5c8a5b63a1 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -490,19 +490,21 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) { } void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) { - _maxParticles = maxParticles; + if (_maxParticles != maxParticles) { + _maxParticles = maxParticles; - // TODO: try to do something smart here and preserve the state of existing particles. + // TODO: try to do something smart here and preserve the state of existing particles. - // resize vectors - _particleLifetimes.resize(_maxParticles); - _particlePositions.resize(_maxParticles); - _particleVelocities.resize(_maxParticles); + // resize vectors + _particleLifetimes.resize(_maxParticles); + _particlePositions.resize(_maxParticles); + _particleVelocities.resize(_maxParticles); - // effectivly clear all particles and start emitting new ones from scratch. - _particleHeadIndex = 0; - _particleTailIndex = 0; - _timeUntilNextEmit = 0.0f; + // effectivly clear all particles and start emitting new ones from scratch. + _particleHeadIndex = 0; + _particleTailIndex = 0; + _timeUntilNextEmit = 0.0f; + } } // because particles are in a ring buffer, this isn't trivial diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index fde6ac40d0..aac7b56bc2 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -66,22 +66,23 @@ void GLBackend::updateInput() { newActivation.set(attrib._slot); } } - + // Manage Activation what was and what is expected now for (unsigned int i = 0; i < newActivation.size(); i++) { bool newState = newActivation[i]; if (newState != _input._attributeActivation[i]) { #if defined(SUPPORT_LEGACY_OPENGL) - if (i < NUM_CLASSIC_ATTRIBS) { + const bool useClientState = i < NUM_CLASSIC_ATTRIBS; +#else + const bool useClientState = false; +#endif + if (useClientState) { if (newState) { glEnableClientState(attributeSlotToClassicAttribName[i]); } else { glDisableClientState(attributeSlotToClassicAttribName[i]); } } else { -#else - { -#endif if (newState) { glEnableVertexAttribArray(i); } else { @@ -89,7 +90,7 @@ void GLBackend::updateInput() { } } (void) CHECK_GL_ERROR(); - + _input._attributeActivation.flip(i); } } @@ -123,29 +124,30 @@ void GLBackend::updateInput() { GLenum type = _elementTypeToGLType[attrib._element.getType()]; GLuint stride = strides[bufferNum]; GLuint pointer = attrib._offset + offsets[bufferNum]; - #if defined(SUPPORT_LEGACY_OPENGL) - if (slot < NUM_CLASSIC_ATTRIBS) { +#if defined(SUPPORT_LEGACY_OPENGL) + const bool useClientState = slot < NUM_CLASSIC_ATTRIBS; +#else + const bool useClientState = false; +#endif + if (useClientState) { switch (slot) { - case Stream::POSITION: - glVertexPointer(count, type, stride, reinterpret_cast(pointer)); - break; - case Stream::NORMAL: - glNormalPointer(type, stride, reinterpret_cast(pointer)); - break; - case Stream::COLOR: - glColorPointer(count, type, stride, reinterpret_cast(pointer)); - break; - case Stream::TEXCOORD: - glTexCoordPointer(count, type, stride, reinterpret_cast(pointer)); - break; + case Stream::POSITION: + glVertexPointer(count, type, stride, reinterpret_cast(pointer)); + break; + case Stream::NORMAL: + glNormalPointer(type, stride, reinterpret_cast(pointer)); + break; + case Stream::COLOR: + glColorPointer(count, type, stride, reinterpret_cast(pointer)); + break; + case Stream::TEXCOORD: + glTexCoordPointer(count, type, stride, reinterpret_cast(pointer)); + break; }; } else { - #else - { - #endif GLboolean isNormalized = attrib._element.isNormalized(); glVertexAttribPointer(slot, count, type, isNormalized, stride, - reinterpret_cast(pointer)); + reinterpret_cast(pointer)); } (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index e23a730370..634545b4dd 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -10,8 +10,8 @@ // #include "Stream.h" - -#include //min max and more + +#include //min max and more using namespace gpu; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 0d9664f1ab..9036f0f6db 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -17,11 +17,11 @@ namespace gpu { -// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated -// with the cube texture -class Texture; -class SphericalHarmonics { -public: +// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated +// with the cube texture +class Texture; +class SphericalHarmonics { +public: glm::vec3 L00 ; float spare0; glm::vec3 L1m1 ; float spare1; glm::vec3 L10 ; float spare2; @@ -44,15 +44,15 @@ public: VINE_STREET_KITCHEN, BREEZEWAY, CAMPUS_SUNSET, - FUNSTON_BEACH_SUNSET, - - NUM_PRESET, + FUNSTON_BEACH_SUNSET, + + NUM_PRESET, }; void assignPreset(int p); void evalFromTexture(const Texture& texture); -}; +}; typedef std::shared_ptr< SphericalHarmonics > SHPointer; class Sampler { @@ -438,7 +438,7 @@ public: explicit operator bool() const { return bool(_texture); } bool operator !() const { return (!_texture); } }; -typedef std::vector TextureViews; +typedef std::vector TextureViews; }; diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 944041730e..a4fa246c93 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -32,12 +32,6 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { } } -#ifdef WIN32 -//warning C4351: new behavior: elements of array 'Assignment::_payload' will be default initialized -// We're disabling this warning because the new behavior which is to initialize the array with 0 is acceptable to us. -#pragma warning(disable:4351) -#endif - Assignment::Assignment() : _uuid(), _command(Assignment::RequestCommand), diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 6c4b3dad75..9a24aabb34 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -132,7 +132,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP; _entity->simulateKinematicMotion(dt); - _entity->setLastSimulated(usecTimestampNow()); // bypass const-ness so we can remember the step const_cast(this)->_lastKinematicStep = thisStep; @@ -401,13 +400,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); - // we only update lastEdited when we're sending new physics data - quint64 lastSimulated = _entity->getLastSimulated(); - _entity->setLastEdited(lastSimulated); - properties.setLastEdited(lastSimulated); + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); + quint64 lastSimulated = _entity->getLastSimulated(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; diff --git a/libraries/physics/src/MeshInfo.cpp b/libraries/physics/src/MeshInfo.cpp deleted file mode 100644 index 29ddc74a98..0000000000 --- a/libraries/physics/src/MeshInfo.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// -// MeshInfo.cpp -// libraries/physics/src -// -// Created by Virendra Singh 2015.02.28 -// 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 "MeshInfo.h" -#include -using namespace meshinfo; - -//class to compute volume, mass, center of mass, and inertia tensor of a mesh. -//origin is the default reference point for generating the tetrahedron from each triangle of the mesh. - -MeshInfo::MeshInfo(vector *vertices, vector *triangles) :\ - _vertices(vertices), - _centerOfMass(Vertex(0.0, 0.0, 0.0)), - _triangles(triangles) -{ - -} - -MeshInfo::~MeshInfo(){ - - _vertices = NULL; - _triangles = NULL; - -} - -inline float MeshInfo::getVolume(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const{ - glm::mat4 tet = { glm::vec4(p1.x, p2.x, p3.x, p4.x), glm::vec4(p1.y, p2.y, p3.y, p4.y), glm::vec4(p1.z, p2.z, p3.z, p4.z), - glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; - return glm::determinant(tet) / 6.0f; -} - -Vertex MeshInfo::getMeshCentroid() const{ - return _centerOfMass; -} - -vector MeshInfo::computeMassProperties(){ - vector volumeAndInertia = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; - Vertex origin(0.0, 0.0, 0.0); - glm::mat3 identity; - float meshVolume = 0.0f; - glm::mat3 globalInertiaTensors(0.0); - - for (unsigned int i = 0; i < _triangles->size(); i += 3){ - Vertex p1 = _vertices->at(_triangles->at(i)); - Vertex p2 = _vertices->at(_triangles->at(i + 1)); - Vertex p3 = _vertices->at(_triangles->at(i + 2)); - float volume = getVolume(p1, p2, p3, origin); - Vertex com = 0.25f * (p1 + p2 + p3); - meshVolume += volume; - _centerOfMass += com * volume; - - //centroid is used for calculating inertia tensor relative to center of mass. - // translate the tetrahedron to its center of mass using P = P - centroid - Vertex p0 = origin - com; - p1 = p1 - com; - p2 = p2 - com; - p3 = p3 - com; - - //Calculate inertia tensor based on Tonon's Formulae given in the paper mentioned below. - //http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf - //Explicit exact formulas for the 3-D tetrahedron inertia tensor in terms of its vertex coordinates - F.Tonon - - float i11 = (volume * 0.1f) * ( - p0.y*p0.y + p0.y*p1.y + p0.y*p2.y + p0.y*p3.y + - p1.y*p1.y + p1.y*p2.y + p1.y*p3.y + - p2.y*p2.y + p2.y*p3.y + - p3.y*p3.y + - p0.z*p0.z + p0.z*p1.z + p0.z*p2.z + p0.z*p3.z + - p1.z*p1.z + p1.z*p2.z + p1.z*p3.z + - p2.z*p2.z + p2.z*p3.z + - p3.z*p3.z); - - float i22 = (volume * 0.1f) * ( - p0.x*p0.x + p0.x*p1.x + p0.x*p2.x + p0.x*p3.x + - p1.x*p1.x + p1.x*p2.x + p1.x*p3.x + - p2.x*p2.x + p2.x*p3.x + - p3.x*p3.x + - p0.z*p0.z + p0.z*p1.z + p0.z*p2.z + p0.z*p3.z + - p1.z*p1.z + p1.z*p2.z + p1.z*p3.z + - p2.z*p2.z + p2.z*p3.z + - p3.z*p3.z); - - float i33 = (volume * 0.1f) * ( - p0.x*p0.x + p0.x*p1.x + p0.x*p2.x + p0.x*p3.x + - p1.x*p1.x + p1.x*p2.x + p1.x*p3.x + - p2.x*p2.x + p2.x*p3.x + - p3.x*p3.x + - p0.y*p0.y + p0.y*p1.y + p0.y*p2.y + p0.y*p3.y + - p1.y*p1.y + p1.y*p2.y + p1.y*p3.y + - p2.y*p2.y + p2.y*p3.y + - p3.y*p3.y); - - float i23 = -(volume * 0.05f) * (2.0f * (p0.y*p0.z + p1.y*p1.z + p2.y*p2.z + p3.y*p3.z) + - p0.y*p1.z + p0.y*p2.z + p0.y*p3.z + - p1.y*p0.z + p1.y*p2.z + p1.y*p3.z + - p2.y*p0.z + p2.y*p1.z + p2.y*p3.z + - p3.y*p0.z + p3.y*p1.z + p3.y*p2.z); - - float i21 = -(volume * 0.05f) * (2.0f * (p0.x*p0.z + p1.x*p1.z + p2.x*p2.z + p3.x*p3.z) + - p0.x*p1.z + p0.x*p2.z + p0.x*p3.z + - p1.x*p0.z + p1.x*p2.z + p1.x*p3.z + - p2.x*p0.z + p2.x*p1.z + p2.x*p3.z + - p3.x*p0.z + p3.x*p1.z + p3.x*p2.z); - - float i31 = -(volume * 0.05f) * (2.0f * (p0.x*p0.y + p1.x*p1.y + p2.x*p2.y + p3.x*p3.y) + - p0.x*p1.y + p0.x*p2.y + p0.x*p3.y + - p1.x*p0.y + p1.x*p2.y + p1.x*p3.y + - p2.x*p0.y + p2.x*p1.y + p2.x*p3.y + - p3.x*p0.y + p3.x*p1.y + p3.x*p2.y); - - //3x3 of local inertia tensors of each tetrahedron. Inertia tensors are the diagonal elements - // | I11 -I12 -I13 | - // I = | -I21 I22 -I23 | - // | -I31 -I32 I33 | - glm::mat3 localInertiaTensors = { Vertex(i11, i21, i31), Vertex(i21, i22, i23), - Vertex(i31, i23, i33) }; - - //Translate the inertia tensors from center of mass to origin - //Parallel axis theorem J = I * m[(R.R)*Identity - RxR] where x is outer cross product - globalInertiaTensors += localInertiaTensors + volume * ((glm::dot(com, com) * identity) - - glm::outerProduct(com, com)); - } - - //Translate accumulated center of mass from each tetrahedron to mesh's center of mass using parallel axis theorem - if (meshVolume == 0){ - return volumeAndInertia; - } - _centerOfMass = (_centerOfMass / meshVolume); - - //Translate the inertia tensors from origin to mesh's center of mass. - globalInertiaTensors = globalInertiaTensors - meshVolume * ((glm::dot(_centerOfMass, _centerOfMass) * - identity) - glm::outerProduct(_centerOfMass, _centerOfMass)); - - volumeAndInertia[0] = meshVolume; - volumeAndInertia[1] = globalInertiaTensors[0][0]; //i11 - volumeAndInertia[2] = globalInertiaTensors[1][1]; //i22 - volumeAndInertia[3] = globalInertiaTensors[2][2]; //i33 - volumeAndInertia[4] = -globalInertiaTensors[2][1]; //i23 or i32 - volumeAndInertia[5] = -globalInertiaTensors[1][0]; //i21 or i12 - volumeAndInertia[6] = -globalInertiaTensors[2][0]; //i13 or i31 - return volumeAndInertia; -} \ No newline at end of file diff --git a/libraries/physics/src/MeshInfo.h b/libraries/physics/src/MeshInfo.h deleted file mode 100644 index 67dbc8b0d6..0000000000 --- a/libraries/physics/src/MeshInfo.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// MeshInfo.h -// libraries/physics/src -// -// Created by Virendra Singh 2015.02.28 -// 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_MeshInfo_h -#define hifi_MeshInfo_h -#include -#include -#include -using namespace std; -namespace meshinfo{ - typedef glm::vec3 Vertex; - class MeshInfo{ - private: - inline float getVolume(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const; - vector computeVolumeAndInertia(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const; - public: - vector *_vertices; - Vertex _centerOfMass; - vector *_triangles; - MeshInfo(vector *vertices, vector *triangles); - ~MeshInfo(); - Vertex getMeshCentroid() const; - vector computeMassProperties(); - }; -} -#endif // hifi_MeshInfo_h \ No newline at end of file diff --git a/libraries/physics/src/MeshMassProperties.cpp b/libraries/physics/src/MeshMassProperties.cpp new file mode 100644 index 0000000000..ee2945422d --- /dev/null +++ b/libraries/physics/src/MeshMassProperties.cpp @@ -0,0 +1,321 @@ +// +// MeshMassProperties.cpp +// libraries/physics/src +// +// Created by Andrew Meadows 2015.05.25 +// 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 +#include + +#include "MeshMassProperties.h" + +// this method is included for unit test verification +void computeBoxInertia(btScalar mass, const btVector3& diagonal, btMatrix3x3& inertia) { + // formula for box inertia tensor: + // + // | y^2 + z^2 0 0 | + // | | + // inertia = M/12 * | 0 z^2 + x^2 0 | + // | | + // | 0 0 x^2 + y^2 | + // + + mass = mass / btScalar(12.0f); + btScalar x = diagonal[0]; + x = mass * x * x; + btScalar y = diagonal[1]; + y = mass * y * y; + btScalar z = diagonal[2]; + z = mass * z * z; + inertia.setIdentity(); + inertia[0][0] = y + z; + inertia[1][1] = z + x; + inertia[2][2] = x + y; +} + +void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& inertia) { + // Computes the inertia tensor of a tetrahedron about its center of mass. + // The tetrahedron is defined by array of four points in its center of mass frame. + // + // The analytic formulas were obtained from Tonon's paper: + // http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf + // http://thescipub.com/PDF/jmssp.2005.8.11.pdf + // + // The inertia tensor has the following form: + // + // | a f e | + // | | + // inertia = | f b d | + // | | + // | e d c | + + const btVector3& p0 = points[0]; + const btVector3& p1 = points[1]; + const btVector3& p2 = points[2]; + const btVector3& p3 = points[3]; + + for (uint32_t i = 0; i < 3; ++i ) { + uint32_t j = (i + 1) % 3; + uint32_t k = (j + 1) % 3; + + // compute diagonal + inertia[i][i] = mass * btScalar(0.1f) * + ( p0[j] * (p0[j] + p1[j] + p2[j] + p3[j]) + + p1[j] * (p1[j] + p2[j] + p3[j]) + + p2[j] * (p2[j] + p3[j]) + + p3[j] * p3[j] + + p0[k] * (p0[k] + p1[k] + p2[k] + p3[k]) + + p1[k] * (p1[k] + p2[k] + p3[k]) + + p2[k] * (p2[k] + p3[k]) + + p3[k] * p3[k] ); + + // compute off-diagonals + inertia[j][k] = inertia[k][j] = - mass * btScalar(0.05f) * + ( btScalar(2.0f) * ( p0[j] * p0[k] + p1[j] * p1[k] + p2[j] * p2[k] + p3[j] * p3[k] ) + + p0[j] * (p1[k] + p2[k] + p3[k]) + + p1[j] * (p0[k] + p2[k] + p3[k]) + + p2[j] * (p0[k] + p1[k] + p3[k]) + + p3[j] * (p0[k] + p1[k] + p2[k]) ); + } +} + +// helper function +void computePointInertia(const btVector3& point, btScalar mass, btMatrix3x3& inertia) { + btScalar distanceSquared = point.length2(); + if (distanceSquared > 0.0f) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar pointi = point[i]; + inertia[i][i] = mass * (distanceSquared - (pointi * pointi)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = - mass * pointi * point[j]; + inertia[i][j] = offDiagonal; + inertia[j][i] = offDiagonal; + } + } + } +} + +// this method is included for unit test verification +void computeTetrahedronInertiaByBruteForce(btVector3* points, btMatrix3x3& inertia) { + // Computes the approximate inertia tensor of a tetrahedron (about frame's origin) + // by integration over the "point" masses. This is numerically expensive so it may + // take a while to complete. + + VectorOfIndices triangles = { + 0, 2, 1, + 0, 3, 2, + 0, 1, 3, + 1, 2, 3 }; + + for (int i = 0; i < 3; ++i) { + inertia[i].setZero(); + } + + // compute normals + btVector3 center = btScalar(0.25f) * (points[0] + points[1] + points[2] + points[3]); + btVector3 normals[4]; + btVector3 pointsOnPlane[4]; + for (int i = 0; i < 4; ++i) { + int t = 3 * i; + btVector3& p0 = points[triangles[t]]; + btVector3& p1 = points[triangles[t + 1]]; + btVector3& p2 = points[triangles[t + 2]]; + normals[i] = ((p1 - p0).cross(p2 - p1)).normalized(); + // make sure normal points away from center + if (normals[i].dot(p0 - center) < btScalar(0.0f)) { + normals[i] *= btScalar(-1.0f); + } + pointsOnPlane[i] = p0; + } + + // compute bounds of integration + btVector3 boxMax = points[0]; + btVector3 boxMin = points[0]; + for (int i = 1; i < 4; ++i) { + for(int j = 0; j < 3; ++j) { + if (points[i][j] > boxMax[j]) { + boxMax[j] = points[i][j]; + } + if (points[i][j] < boxMin[j]) { + boxMin[j] = points[i][j]; + } + } + } + + // compute step size + btVector3 diagonal = boxMax - boxMin; + btScalar maxDimension = diagonal[0]; + if (diagonal[1] > maxDimension) { + maxDimension = diagonal[1]; + } + if (diagonal[2] > maxDimension) { + maxDimension = diagonal[2]; + } + btScalar resolutionOfIntegration = btScalar(400.0f); + btScalar delta = maxDimension / resolutionOfIntegration; + btScalar deltaVolume = delta * delta * delta; + + // integrate over three dimensions + btMatrix3x3 deltaInertia; + btScalar XX = boxMax[0]; + btScalar YY = boxMax[1]; + btScalar ZZ = boxMax[2]; + btScalar x = boxMin[0]; + while(x < XX) { + btScalar y = boxMin[1]; + while (y < YY) { + btScalar z = boxMin[2]; + while (z < ZZ) { + btVector3 p(x, y, z); + // the point is inside the shape if it is behind all face planes + bool pointInside = true; + for (int i = 0; i < 4; ++i) { + if ((p - pointsOnPlane[i]).dot(normals[i]) > btScalar(0.0f)) { + pointInside = false; + break; + } + } + if (pointInside) { + // this point contributes to the total + computePointInertia(p, deltaVolume, deltaInertia); + inertia += deltaInertia; + } + z += delta; + } + y += delta; + } + x += delta; + } +} + +btScalar computeTetrahedronVolume(btVector3* points) { + // Assumes triangle {1, 2, 3} is wound according to the right-hand-rule. + // NOTE: volume may be negative, in which case the tetrahedron contributes negatively to totals + // volume = (face_area * face_normal).dot(face_to_far_point) / 3.0 + // (face_area * face_normal) = side0.cross(side1) / 2.0 + return ((points[2] - points[1]).cross(points[3] - points[2])).dot(points[3] - points[0]) / btScalar(6.0f); +} + +void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { + // Parallel Axis Theorem says: + // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // where R*R = inside product + // R(x)R = outside product + // E = identity matrix + + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar shifti = shift[i]; + inertia[i][i] += mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = mass * shifti * shift[j]; + inertia[i][j] -= offDiagonal; + inertia[j][i] -= offDiagonal; + } + } + } +} + +// helper function +void applyInverseParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { + // Parallel Axis Theorem says: + // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // So the inverse would be: + // + // Icm = Ishifted - M * [ (R*R)E - R(x)R ] + + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar shifti = shift[i]; + inertia[i][i] -= mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = mass * shifti * shift[j]; + inertia[i][j] += offDiagonal; + inertia[j][i] += offDiagonal; + } + } + } +} + +MeshMassProperties::MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { + computeMassProperties(points, triangleIndices); +} + +void MeshMassProperties::computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { + // We process the mesh one triangle at a time. Each triangle defines a tetrahedron + // relative to some local point p0 (which we chose to be the local origin for convenience). + // Each tetrahedron contributes to the three totals: volume, centerOfMass, and inertiaTensor. + // + // We assume the mesh triangles are wound using the right-hand-rule, such that the + // triangle's points circle counter-clockwise about its face normal. + // + + // initialize the totals + _volume = btScalar(0.0f); + btVector3 weightedCenter; + weightedCenter.setZero(); + for (uint32_t i = 0; i < 3; ++i) { + _inertia[i].setZero(); + } + + // create some variables to hold temporary results + uint32_t numPoints = points.size(); + const btVector3 p0(0.0f, 0.0f, 0.0f); + btMatrix3x3 tetraInertia; + btMatrix3x3 doubleDebugInertia; + btVector3 tetraPoints[4]; + btVector3 center; + + // loop over triangles + uint32_t numTriangles = triangleIndices.size() / 3; + for (uint32_t i = 0; i < numTriangles; ++i) { + uint32_t t = 3 * i; + assert(triangleIndices[t] < numPoints); + assert(triangleIndices[t + 1] < numPoints); + assert(triangleIndices[t + 2] < numPoints); + + // extract raw vertices + tetraPoints[0] = p0; + tetraPoints[1] = points[triangleIndices[t]]; + tetraPoints[2] = points[triangleIndices[t + 1]]; + tetraPoints[3] = points[triangleIndices[t + 2]]; + + // compute volume + btScalar volume = computeTetrahedronVolume(tetraPoints); + + // compute center + // NOTE: since tetraPoints[0] is the origin, we don't include it in the sum + center = btScalar(0.25f) * (tetraPoints[1] + tetraPoints[2] + tetraPoints[3]); + + // shift vertices so that center of mass is at origin + tetraPoints[0] -= center; + tetraPoints[1] -= center; + tetraPoints[2] -= center; + tetraPoints[3] -= center; + + // compute inertia tensor then shift it to origin-frame + computeTetrahedronInertia(volume, tetraPoints, tetraInertia); + applyParallelAxisTheorem(tetraInertia, center, volume); + + // tally results + weightedCenter += volume * center; + _volume += volume; + _inertia += tetraInertia; + } + + _centerOfMass = weightedCenter / _volume; + + applyInverseParallelAxisTheorem(_inertia, _centerOfMass, _volume); +} + diff --git a/libraries/physics/src/MeshMassProperties.h b/libraries/physics/src/MeshMassProperties.h new file mode 100644 index 0000000000..208955d3a6 --- /dev/null +++ b/libraries/physics/src/MeshMassProperties.h @@ -0,0 +1,61 @@ +// +// MeshMassProperties.h +// libraries/physics/src +// +// Created by Andrew Meadows 2015.05.25 +// 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 +// + +#ifndef hifi_MeshMassProperties_h +#define hifi_MeshMassProperties_h + +#include +#include + +#include + +typedef std::vector VectorOfPoints; +typedef std::vector VectorOfIndices; + +#define EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +#ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +void computeBoxInertia(btScalar mass, const btVector3& diagonal, btMatrix3x3& I); + +// mass = input mass of tetrahedron +// points = input array of four points with center of mass at origin +// I = output inertia of tetrahedron about its center of mass +void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& I); +void computeTetrahedronInertiaByBruteForce(btVector3* points, btMatrix3x3& I); + +btScalar computeTetrahedronVolume(btVector3* points); + +void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass); +#endif // EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST + +// Given a closed mesh with right-hand triangles a MeshMassProperties instance will compute +// its mass properties: +// +// volume +// center-of-mass +// normalized interia tensor about center of mass +// +class MeshMassProperties { +public: + + // the mass properties calculation is done in the constructor, so if the mesh is complex + // then the construction could be computationally expensiuve. + MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); + + // compute the mass properties of a new mesh + void computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); + + // harveste the mass properties from these public data members + btScalar _volume = 1.0f; + btVector3 _centerOfMass = btVector3(0.0f, 0.0f, 0.0f); + btMatrix3x3 _inertia = btMatrix3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); +}; + +#endif // _hifi_MeshMassProperties_h diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8a9ee4bf6d..00c9a13387 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -62,8 +62,8 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); _simpleProgram = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); _viewState = viewState; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f7280fc0ec..cf5a0d58da 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -815,9 +815,9 @@ void GeometryCache::renderSolidCube(gpu::Batch& batch, float size, const glm::ve const int VERTEX_STRIDE = sizeof(GLfloat) * FLOATS_PER_VERTEX * 2; // vertices and normals const int NORMALS_OFFSET = sizeof(GLfloat) * FLOATS_PER_VERTEX; - if (!_solidCubeVerticies.contains(size)) { + if (!_solidCubeVertices.contains(size)) { gpu::BufferPointer verticesBuffer(new gpu::Buffer()); - _solidCubeVerticies[size] = verticesBuffer; + _solidCubeVertices[size] = verticesBuffer; GLfloat* vertexData = new GLfloat[vertexPoints * 2]; // vertices and normals GLfloat* vertex = vertexData; @@ -892,7 +892,7 @@ void GeometryCache::renderSolidCube(gpu::Batch& batch, float size, const glm::ve colorBuffer->append(sizeof(colors), (gpu::Byte*) colors); } - gpu::BufferPointer verticesBuffer = _solidCubeVerticies[size]; + gpu::BufferPointer verticesBuffer = _solidCubeVertices[size]; gpu::BufferPointer colorBuffer = _solidCubeColors[colorKey]; const int VERTICES_SLOT = 0; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 9b21eab2d6..b438eb2d3b 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -270,7 +270,7 @@ private: QHash _cubeColors; gpu::BufferPointer _wireCubeIndexBuffer; - QHash _solidCubeVerticies; + QHash _solidCubeVertices; QHash _solidCubeColors; gpu::BufferPointer _solidCubeIndexBuffer; diff --git a/libraries/render-utils/src/RenderUtil.h b/libraries/render-utils/src/RenderUtil.h index cc823dc177..8c1b1e12e7 100644 --- a/libraries/render-utils/src/RenderUtil.h +++ b/libraries/render-utils/src/RenderUtil.h @@ -12,8 +12,6 @@ #ifndef hifi_RenderUtil_h #define hifi_RenderUtil_h -#include - /// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (sMin, tMin) to (sMax, tMax). void renderFullscreenQuad(float sMin = 0.0f, float sMax = 1.0f, float tMin = 0.0f, float tMax = 1.0f); diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp new file mode 100644 index 0000000000..d081c0480a --- /dev/null +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -0,0 +1,498 @@ +// +// TextRenderer3D.cpp +// interface/src/ui +// +// Created by Andrzej Kapolka on 4/24/13. +// 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 "TextRenderer3D.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "GLMHelpers.h" +#include "MatrixStack.h" +#include "RenderUtilsLogging.h" + +#include "sdf_text3D_vert.h" +#include "sdf_text3D_frag.h" + +#include "GeometryCache.h" +#include "DeferredLightingEffect.h" + +// FIXME support the shadow effect, or remove it from the API +// FIXME figure out how to improve the anti-aliasing on the +// interior of the outline fonts +const float DEFAULT_POINT_SIZE = 12; + +// Helper functions for reading binary data from an IO device +template +void readStream(QIODevice& in, T& t) { + in.read((char*) &t, sizeof(t)); +} + +template +void readStream(QIODevice& in, T (&t)[N]) { + in.read((char*) t, N); +} + +template +void fillBuffer(QBuffer& buffer, T (&t)[N]) { + buffer.setData((const char*) t, N); +} + +// stores the font metrics for a single character +struct Glyph3D { + QChar c; + glm::vec2 texOffset; + glm::vec2 texSize; + glm::vec2 size; + glm::vec2 offset; + float d; // xadvance - adjusts character positioning + size_t indexOffset; + + // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect + QRectF bounds() const { return glmToRect(offset, size).translated(0.0f, -size.y); } + QRectF textureBounds() const { return glmToRect(texOffset, texSize); } + + void read(QIODevice& in); +}; + +void Glyph3D::read(QIODevice& in) { + uint16_t charcode; + readStream(in, charcode); + c = charcode; + readStream(in, texOffset); + readStream(in, size); + readStream(in, offset); + readStream(in, d); + texSize = size; +} + +struct TextureVertex { + glm::vec2 pos; + glm::vec2 tex; + TextureVertex() {} + TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} +}; + +struct QuadBuilder { + TextureVertex vertices[4]; + QuadBuilder(const glm::vec2& min, const glm::vec2& size, + const glm::vec2& texMin, const glm::vec2& texSize) { + // min = bottomLeft + vertices[0] = TextureVertex(min, + texMin + glm::vec2(0.0f, texSize.y)); + vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), + texMin + texSize); + vertices[2] = TextureVertex(min + size, + texMin + glm::vec2(texSize.x, 0.0f)); + vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y), + texMin); + } + QuadBuilder(const Glyph3D& glyph, const glm::vec2& offset) : + QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, + glyph.texOffset, glyph.texSize) {} + +}; + +class Font3D { +public: + Font3D(); + + void read(QIODevice& path); + + glm::vec2 computeExtent(const QString& str) const; + float getRowHeight() const { return _rowHeight; } + + // Render string to batch + void drawString(gpu::Batch& batch, float x, float y, const QString& str, + const glm::vec4& color, TextRenderer3D::EffectType effectType, + const glm::vec2& bound); + +private: + QStringList tokenizeForWrapping(const QString& str) const; + QStringList splitLines(const QString& str) const; + glm::vec2 computeTokenExtent(const QString& str) const; + + const Glyph3D& getGlyph(const QChar& c) const; + + void setupGPU(); + + // maps characters to cached glyph info + // HACK... the operator[] const for QHash returns a + // copy of the value, not a const value reference, so + // we declare the hash as mutable in order to avoid such + // copies + mutable QHash _glyphs; + + // Font characteristics + QString _family; + float _fontSize = 0.0f; + float _rowHeight = 0.0f; + float _leading = 0.0f; + float _ascent = 0.0f; + float _descent = 0.0f; + float _spaceWidth = 0.0f; + + bool _initialized = false; + + // gpu structures + gpu::PipelinePointer _pipeline; + gpu::TexturePointer _texture; + gpu::Stream::FormatPointer _format; + gpu::BufferPointer _verticesBuffer; + gpu::BufferStreamPointer _stream; + unsigned int _numVertices = 0; + + int _fontLoc = -1; + int _outlineLoc = -1; + int _colorLoc = -1; + + // last string render characteristics + QString _lastStringRendered; + glm::vec2 _lastBounds; +}; + +static QHash LOADED_FONTS; + +Font3D* loadFont3D(QIODevice& fontFile) { + Font3D* result = new Font3D(); + result->read(fontFile); + return result; +} + +Font3D* loadFont3D(const QString& family) { + if (!LOADED_FONTS.contains(family)) { + + const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff"; + const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff"; + const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff"; + const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff"; + + QString loadFilename; + + if (family == MONO_FONT_FAMILY) { + loadFilename = SDFF_COURIER_PRIME_FILENAME; + } else if (family == INCONSOLATA_FONT_FAMILY) { + loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME; + } else if (family == SANS_FONT_FAMILY) { + loadFilename = SDFF_ROBOTO_FILENAME; + } else { + if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) { + loadFilename = SDFF_TIMELESS_FILENAME; + } else { + LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY]; + } + } + + if (!loadFilename.isEmpty()) { + QFile fontFile(loadFilename); + fontFile.open(QIODevice::ReadOnly); + + qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System."; + + LOADED_FONTS[family] = loadFont3D(fontFile); + } + } + return LOADED_FONTS[family]; +} + +Font3D::Font3D() { + static bool fontResourceInitComplete = false; + if (!fontResourceInitComplete) { + Q_INIT_RESOURCE(fonts); + fontResourceInitComplete = true; + } +} + +// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member +const Glyph3D& Font3D::getGlyph(const QChar& c) const { + if (!_glyphs.contains(c)) { + return _glyphs[QChar('?')]; + } + return _glyphs[c]; +} + +QStringList Font3D::splitLines(const QString& str) const { + return str.split('\n'); +} + +QStringList Font3D::tokenizeForWrapping(const QString& str) const { + QStringList tokens; + for(auto line : splitLines(str)) { + if (!tokens.empty()) { + tokens << QString('\n'); + } + tokens << line.split(' '); + } + return tokens; +} + +glm::vec2 Font3D::computeTokenExtent(const QString& token) const { + glm::vec2 advance(0, _fontSize); + foreach(QChar c, token) { + Q_ASSERT(c != '\n'); + advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; + } + return advance; +} + +glm::vec2 Font3D::computeExtent(const QString& str) const { + glm::vec2 extent = glm::vec2(0.0f, 0.0f); + + QStringList tokens = splitLines(str); + foreach(const QString& token, tokens) { + glm::vec2 tokenExtent = computeTokenExtent(token); + extent.x = std::max(tokenExtent.x, extent.x); + } + extent.y = tokens.count() * _rowHeight; + + return extent; +} + +void Font3D::read(QIODevice& in) { + uint8_t header[4]; + readStream(in, header); + if (memcmp(header, "SDFF", 4)) { + qFatal("Bad SDFF file"); + } + + uint16_t version; + readStream(in, version); + + // read font name + _family = ""; + if (version > 0x0001) { + char c; + readStream(in, c); + while (c) { + _family += c; + readStream(in, c); + } + } + + // read font data + readStream(in, _leading); + readStream(in, _ascent); + readStream(in, _descent); + readStream(in, _spaceWidth); + _fontSize = _ascent + _descent; + _rowHeight = _fontSize + _leading; + + // Read character count + uint16_t count; + readStream(in, count); + // read metrics data for each character + QVector glyphs(count); + // std::for_each instead of Qt foreach because we need non-const references + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph3D& g) { + g.read(in); + }); + + // read image data + QImage image; + if (!image.loadFromData(in.readAll(), "PNG")) { + qFatal("Failed to read SDFF image"); + } + + _glyphs.clear(); + glm::vec2 imageSize = toGlm(image.size()); + foreach(Glyph3D g, glyphs) { + // Adjust the pixel texture coordinates into UV coordinates, + g.texSize /= imageSize; + g.texOffset /= imageSize; + // store in the character to glyph hash + _glyphs[g.c] = g; + }; + + image = image.convertToFormat(QImage::Format_RGBA8888); + + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); +} + +void Font3D::setupGPU() { + if (!_initialized) { + _initialized = true; + + // Setup render pipeline + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _fontLoc = program->getTextures().findLocation("Font"); + _outlineLoc = program->getUniforms().findLocation("Outline"); + _colorLoc = program->getUniforms().findLocation("Color"); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + + // Sanity checks + static const int OFFSET = offsetof(TextureVertex, tex); + assert(OFFSET == sizeof(glm::vec2)); + assert(sizeof(glm::vec2) == 2 * sizeof(float)); + assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2)); + assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); + + // Setup rendering structures + _format.reset(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); + _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); + } +} + +void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + TextRenderer3D::EffectType effectType, const glm::vec2& bounds) { + if (str == "") { + return; + } + + if (str != _lastStringRendered || bounds != _lastBounds) { + _verticesBuffer.reset(new gpu::Buffer()); + _numVertices = 0; + _lastStringRendered = str; + _lastBounds = bounds; + + // Top left of text + glm::vec2 advance = glm::vec2(x, y); + foreach(const QString& token, tokenizeForWrapping(str)) { + bool isNewLine = (token == QString('\n')); + bool forceNewLine = false; + + // Handle wrapping + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > x + bounds.x)) { + // We are out of the x bound, force new line + forceNewLine = true; + } + if (isNewLine || forceNewLine) { + // Character return, move the advance to a new line + advance = glm::vec2(x, advance.y - _rowHeight); + + if (isNewLine) { + // No need to draw anything, go directly to next token + continue; + } else if (computeExtent(token).x > bounds.x) { + // token will never fit, stop drawing + break; + } + } + if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { + // We are out of the y bound, stop drawing + break; + } + + // Draw the token + if (!isNewLine) { + for (auto c : token) { + auto glyph = _glyphs[c]; + + QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize)); + _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); + _numVertices += 4; + + // Advance by glyph size + advance.x += glyph.d; + } + + // Add space after all non return tokens + advance.x += _spaceWidth; + } + } + } + + setupGPU(); + batch.setPipeline(_pipeline); + batch.setUniformTexture(_fontLoc, _texture); + batch._glUniform1f(_outlineLoc, (effectType == TextRenderer3D::OUTLINE_EFFECT) ? 1.0f : 0.0f); + batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color); + + batch.setInputFormat(_format); + batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.draw(gpu::QUADS, _numVertices, 0); +} + +TextRenderer3D* TextRenderer3D::getInstance(const char* family, float pointSize, + int weight, bool italic, EffectType effect, int effectThickness, + const QColor& color) { + if (pointSize < 0) { + pointSize = DEFAULT_POINT_SIZE; + } + return new TextRenderer3D(family, pointSize, weight, italic, effect, + effectThickness, color); +} + +TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic, + EffectType effect, int effectThickness, const QColor& color) : + _effectType(effect), + _effectThickness(effectThickness), + _pointSize(pointSize), + _color(toGlm(color)), + _font(loadFont3D(family)) { + if (!_font) { + qWarning() << "Unable to load font with family " << family; + _font = loadFont3D("Courier"); + } + if (1 != _effectThickness) { + qWarning() << "Effect thickness not current supported"; + } + if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) { + qWarning() << "Effect thickness not current supported"; + } +} + +TextRenderer3D::~TextRenderer3D() { +} + +glm::vec2 TextRenderer3D::computeExtent(const QString& str) const { + if (_font) { + return _font->computeExtent(str); + } + return glm::vec2(0.0f, 0.0f); +} + +float TextRenderer3D::getRowHeight() const { + if (_font) { + return _font->getRowHeight(); + } + return 0.0f; +} + +void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + const glm::vec2& bounds) { + // The font does all the OpenGL work + if (_font) { + glm::vec4 actualColor(color); + if (actualColor.r < 0) { + actualColor = _color; + } + _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); + } +} + diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h new file mode 100644 index 0000000000..8f55d0c977 --- /dev/null +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -0,0 +1,77 @@ +// +// TextRenderer3D.h +// interface/src/ui +// +// Created by Andrzej Kapolka on 4/26/13. +// 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_TextRenderer3D_h +#define hifi_TextRenderer3D_h + +#include +#include + +// the standard sans serif font family +#define SANS_FONT_FAMILY "Helvetica" + +// the standard sans serif font family +#define SERIF_FONT_FAMILY "Timeless" + +// the standard mono font family +#define MONO_FONT_FAMILY "Courier" + +// the Inconsolata font family +#ifdef Q_OS_WIN +#define INCONSOLATA_FONT_FAMILY "Fixedsys" +#define INCONSOLATA_FONT_WEIGHT QFont::Normal +#else +#define INCONSOLATA_FONT_FAMILY "Inconsolata" +#define INCONSOLATA_FONT_WEIGHT QFont::Bold +#endif + +namespace gpu { +class Batch; +} +class Font3D; + +// TextRenderer3D is actually a fairly thin wrapper around a Font class +// defined in the cpp file. +class TextRenderer3D { +public: + enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT }; + + static TextRenderer3D* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false, + EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255)); + + ~TextRenderer3D(); + + glm::vec2 computeExtent(const QString& str) const; + float getRowHeight() const; + + void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); + +private: + TextRenderer3D(const char* family, float pointSize = -1, int weight = -1, bool italic = false, + EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255)); + + // the type of effect to apply + const EffectType _effectType; + + // the thickness of the effect + const int _effectThickness; + + const float _pointSize; + + // text color + const glm::vec4 _color; + + Font3D* _font; +}; + + +#endif // hifi_TextRenderer3D_h diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 6facd99ff0..97385cb060 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -296,7 +296,7 @@ GLuint TextureCache::getShadowDepthTextureID() { } /// Returns a texture version of an image file -gpu::TexturePointer TextureCache::getImageTexture(const QString & path) { +gpu::TexturePointer TextureCache::getImageTexture(const QString& path) { QImage image = QImage(path).mirrored(false, true); gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); @@ -383,9 +383,9 @@ ImageReader::ImageReader(const QWeakPointer& texture, TextureType type _content(content) { } -std::once_flag onceListSuppoertedFormatsflag; +std::once_flag onceListSupportedFormatsflag; void listSupportedImageFormats() { - std::call_once(onceListSuppoertedFormatsflag, [](){ + std::call_once(onceListSupportedFormatsflag, [](){ auto supportedFormats = QImageReader::supportedImageFormats(); QString formats; foreach(const QByteArray& f, supportedFormats) { diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index 603ab3a807..ba7176b2a4 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -56,7 +56,7 @@ public: const gpu::TexturePointer& getBlueTexture(); /// Returns a texture version of an image file - gpu::TexturePointer getImageTexture(const QString & path); + static gpu::TexturePointer getImageTexture(const QString& path); /// Loads a texture from the specified URL. NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, bool dilatable = false, diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf new file mode 100644 index 0000000000..3980045d08 --- /dev/null +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -0,0 +1,47 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// sdf_text.frag +// fragment shader +// +// Created by Bradley Austin Davis on 2015-02-04 +// Based on fragment shader code from +// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +uniform sampler2D Font; +uniform float Outline; +uniform vec4 Color; + +const float gamma = 2.6; +const float smoothing = 100.0; +const float interiorCutoff = 0.8; +const float outlineExpansion = 0.2; + +void main() { + // retrieve signed distance + float sdf = texture2D(Font, gl_TexCoord[0].xy).g; + if (Outline == 1.0f) { + if (sdf > interiorCutoff) { + sdf = 1.0 - sdf; + } else { + sdf += outlineExpansion; + } + } + // perform adaptive anti-aliasing of the edges + // The larger we're rendering, the less anti-aliasing we need + float s = smoothing * length(fwidth(gl_TexCoord[0])); + float w = clamp( s, 0.0, 0.5); + float a = smoothstep(0.5 - w, 0.5 + w, sdf); + + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + if (a < 0.01) { + discard; + } + + // final color + gl_FragColor = vec4(Color.rgb, a); +} \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv new file mode 100644 index 0000000000..f7c35a257c --- /dev/null +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -0,0 +1,23 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// sdf_text.vert +// vertex shader +// +// Created by Brad Davis on 10/14/13. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +void main() { + gl_TexCoord[0] = gl_MultiTexCoord0; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> +} \ No newline at end of file diff --git a/tests/physics/src/MeshInfoTests.cpp b/tests/physics/src/MeshInfoTests.cpp deleted file mode 100644 index 221ffa117c..0000000000 --- a/tests/physics/src/MeshInfoTests.cpp +++ /dev/null @@ -1,237 +0,0 @@ -// -// MeshInfoTests.cpp -// tests/physics/src -// -// Created by Virendra Singh on 2015.03.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 "MeshInfoTests.h" -const float epsilon = 0.015f; -void MeshInfoTests::testWithTetrahedron(){ - glm::vec3 p0(8.33220, -11.86875, 0.93355); - glm::vec3 p1(0.75523, 5.00000, 16.37072); - glm::vec3 p2(52.61236, 5.00000, -5.38580); - glm::vec3 p3(2.00000, 5.00000, 3.00000); - glm::vec3 centroid(15.92492, 0.782813, 3.72962); - - //translate the tetrahedron so that its apex is on origin - glm::vec3 p11 = p1 - p0; - glm::vec3 p22 = p2 - p0; - glm::vec3 p33 = p3 - p0; - vector vertices = { p11, p22, p33 }; - vector triangles = { 0, 1, 2 }; - - float volume = 1873.233236f; - float inertia_a = 43520.33257f; - //actual should be 194711.28938f. But for some reason it becomes 194711.296875 during - //runtime due to how floating points are stored. - float inertia_b = 194711.289f; - float inertia_c = 191168.76173f; - float inertia_aa = 4417.66150f; - float inertia_bb = -46343.16662f; - float inertia_cc = 11996.20119f; - - meshinfo::MeshInfo meshinfo(&vertices,&triangles); - vector voumeAndInertia = meshinfo.computeMassProperties(); - glm::vec3 tetCenterOfMass = meshinfo.getMeshCentroid(); - - //get original center of mass - tetCenterOfMass = tetCenterOfMass + p0; - glm::vec3 diff = centroid - tetCenterOfMass; - std::cout << std::setprecision(12); - //test if centroid is correct - if (diff.x > epsilon || diff.y > epsilon || diff.z > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Centroid is incorrect : Expected = " << centroid.x << " " << - centroid.y << " " << centroid.z << ", actual = " << tetCenterOfMass.x << " " << tetCenterOfMass.y << - " " << tetCenterOfMass.z << std::endl; - } - - //test if volume is correct - if (abs(volume - voumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << " " << - ", actual = " << voumeAndInertia.at(0) << std::endl; - } - - //test if moment of inertia with respect to x axis is correct - if (abs(inertia_a - (voumeAndInertia.at(1))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to x axis is incorrect : Expected = " << - inertia_a << " " << ", actual = " << voumeAndInertia.at(1) << std::endl; - } - - //test if moment of inertia with respect to y axis is correct - if (abs(inertia_b - voumeAndInertia.at(2)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to y axis is incorrect : Expected = " << - inertia_b << " " << ", actual = " << (voumeAndInertia.at(2)) << std::endl; - } - - //test if moment of inertia with respect to z axis is correct - if (abs(inertia_c - (voumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to z axis is incorrect : Expected = " << - inertia_c << " " << ", actual = " << (voumeAndInertia.at(3)) << std::endl; - } - - //test if product of inertia with respect to x axis is correct - if (abs(inertia_aa - (voumeAndInertia.at(4))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to x axis is incorrect : Expected = " << - inertia_aa << " " << ", actual = " << (voumeAndInertia.at(4)) << std::endl; - } - - //test if product of inertia with respect to y axis is correct - if (abs(inertia_bb - (voumeAndInertia.at(5))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to y axis is incorrect : Expected = " << - inertia_bb << " " << ", actual = " << (voumeAndInertia.at(5)) << std::endl; - } - - //test if product of inertia with respect to z axis is correct - if (abs(inertia_cc - (voumeAndInertia.at(6))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to z axis is incorrect : Expected = " << - inertia_cc << " " << ", actual = " << (voumeAndInertia.at(6)) << std::endl; - } - -} - -void MeshInfoTests::testWithTetrahedronAsMesh(){ - glm::vec3 p0(8.33220, -11.86875, 0.93355); - glm::vec3 p1(0.75523, 5.00000, 16.37072); - glm::vec3 p2(52.61236, 5.00000, -5.38580); - glm::vec3 p3(2.00000, 5.00000, 3.00000); - glm::vec3 centroid(15.92492, 0.782813, 3.72962); - /* TODO: actually test inertia/volume calculations here - //float volume = 1873.233236f; - //runtime due to how floating points are stored. - float inertia_a = 43520.33257f; - float inertia_b = 194711.289f; - float inertia_c = 191168.76173f; - float inertia_aa = 4417.66150f; - float inertia_bb = -46343.16662f; - float inertia_cc = 11996.20119f; - */ - std::cout << std::setprecision(12); - vector vertices = { p0, p1, p2, p3 }; - vector triangles = { 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 3 }; - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - std::cout << volumeAndInertia[0] << " " << volumeAndInertia[1] << " " << volumeAndInertia[2] - << " " << volumeAndInertia[3] - << " " << volumeAndInertia[4] - << " " << volumeAndInertia[5] << " " << volumeAndInertia[6] << std::endl; - - //translate the tetrahedron so that the model is placed at origin i.e. com is at origin - p0 -= centroid; - p1 -= centroid; - p2 -= centroid; - p3 -= centroid; -} - -void MeshInfoTests::testWithCube(){ - glm::vec3 p0(1.0, -1.0, -1.0); - glm::vec3 p1(1.0, -1.0, 1.0); - glm::vec3 p2(-1.0, -1.0, 1.0); - glm::vec3 p3(-1.0, -1.0, -1.0); - glm::vec3 p4(1.0, 1.0, -1.0); - glm::vec3 p5(1.0, 1.0, 1.0); - glm::vec3 p6(-1.0, 1.0, 1.0); - glm::vec3 p7(-1.0, 1.0, -1.0); - vector vertices; - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - vertices.push_back(p3); - vertices.push_back(p4); - vertices.push_back(p5); - vertices.push_back(p6); - vertices.push_back(p7); - std::cout << std::setprecision(10); - vector triangles = { 0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, - 7, 2, 7, 3, 4, 0, 3, 4, 3, 7 }; - glm::vec3 centerOfMass(0.0, 0.0, 0.0); - double volume = 8.0; - double side = 2.0; - double inertia = (volume * side * side) / 6.0; //inertia of a unit cube is (mass * side * side) /6 - - //test with origin as reference point - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - if (abs(centerOfMass.x - massProp.getMeshCentroid().x) > epsilon || abs(centerOfMass.y - massProp.getMeshCentroid().y) > epsilon || - abs(centerOfMass.z - massProp.getMeshCentroid().z) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Center of mass is incorrect : Expected = " << centerOfMass.x << " " << - centerOfMass.y << " " << centerOfMass.z << ", actual = " << massProp.getMeshCentroid().x << " " << - massProp.getMeshCentroid().y << " " << massProp.getMeshCentroid().z << std::endl; - } - - if (abs(volume - volumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << - ", actual = " << volumeAndInertia.at(0) << std::endl; - } - - if (abs(inertia - (volumeAndInertia.at(1))) > epsilon || abs(inertia - (volumeAndInertia.at(2))) > epsilon || - abs(inertia - (volumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia is incorrect : Expected = " << inertia << " " << - inertia << " " << inertia << ", actual = " << (volumeAndInertia.at(1)) << " " << (volumeAndInertia.at(2)) << - " " << (volumeAndInertia.at(3)) << std::endl; - } -} - -void MeshInfoTests::testWithUnitCube() -{ - glm::vec3 p0(0, 0, 1); - glm::vec3 p1(1, 0, 1); - glm::vec3 p2(0, 1, 1); - glm::vec3 p3(1, 1, 1); - glm::vec3 p4(0, 0, 0); - glm::vec3 p5(1, 0, 0); - glm::vec3 p6(0, 1, 0); - glm::vec3 p7(1, 1, 0); - vector vertices; - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - vertices.push_back(p3); - vertices.push_back(p4); - vertices.push_back(p5); - vertices.push_back(p6); - vertices.push_back(p7); - vector triangles = { 0, 1, 2, 1, 3, 2, 2, 3, 7, 2, 7, 6, 1, 7, 3, 1, 5, 7, 6, 7, 4, 7, 5, 4, 0, 4, 1, - 1, 4, 5, 2, 6, 4, 0, 2, 4 }; - glm::vec3 centerOfMass(0.5, 0.5, 0.5); - double volume = 1.0; - double side = 1.0; - double inertia = (volume * side * side) / 6.0; //inertia of a unit cube is (mass * side * side) /6 - std::cout << std::setprecision(10); - - //test with origin as reference point - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - if (abs(centerOfMass.x - massProp.getMeshCentroid().x) > epsilon || abs(centerOfMass.y - massProp.getMeshCentroid().y) > - epsilon || abs(centerOfMass.z - massProp.getMeshCentroid().z) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Center of mass is incorrect : Expected = " << centerOfMass.x << - " " << centerOfMass.y << " " << centerOfMass.z << ", actual = " << massProp.getMeshCentroid().x << " " << - massProp.getMeshCentroid().y << " " << massProp.getMeshCentroid().z << std::endl; - } - - if (abs(volume - volumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << - ", actual = " << volumeAndInertia.at(0) << std::endl; - } - - if (abs(inertia - (volumeAndInertia.at(1))) > epsilon || abs(inertia - (volumeAndInertia.at(2))) > epsilon || - abs(inertia - (volumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia is incorrect : Expected = " << inertia << " " << - inertia << " " << inertia << ", actual = " << (volumeAndInertia.at(1)) << " " << (volumeAndInertia.at(2)) << - " " << (volumeAndInertia.at(3)) << std::endl; - } -} -void MeshInfoTests::runAllTests(){ - testWithTetrahedron(); - testWithTetrahedronAsMesh(); - testWithUnitCube(); - testWithCube(); -} diff --git a/tests/physics/src/MeshInfoTests.h b/tests/physics/src/MeshInfoTests.h deleted file mode 100644 index 0ddd8d0944..0000000000 --- a/tests/physics/src/MeshInfoTests.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MeshInfoTests.h -// tests/physics/src -// -// Created by Virendra Singh on 2015.03.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_MeshInfoTests_h -#define hifi_MeshInfoTests_h -namespace MeshInfoTests{ - void testWithTetrahedron(); - void testWithUnitCube(); - void testWithCube(); - void runAllTests(); - void testWithTetrahedronAsMesh(); -} -#endif // hifi_MeshInfoTests_h \ No newline at end of file diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp new file mode 100644 index 0000000000..ebb762aa55 --- /dev/null +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -0,0 +1,459 @@ +// +// MeshMassProperties.cpp +// tests/physics/src +// +// Created by Virendra Singh on 2015.03.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 "MeshMassPropertiesTests.h" + +//#define VERBOSE_UNIT_TESTS + +const btScalar acceptableRelativeError(1.0e-5f); +const btScalar acceptableAbsoluteError(1.0e-4f); + +void printMatrix(const std::string& name, const btMatrix3x3& matrix) { + std::cout << name << " = [" << std::endl; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + std::cout << " " << matrix[i][j]; + } + std::cout << std::endl; + } + std::cout << "]" << std::endl; +} + +void pushTriangle(VectorOfIndices& indices, uint32_t a, uint32_t b, uint32_t c) { + indices.push_back(a); + indices.push_back(b); + indices.push_back(c); +} + +void MeshMassPropertiesTests::testParallelAxisTheorem() { +#ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST + // verity we can compute the inertia tensor of a box in two different ways: + // (a) as one box + // (b) as a combination of two partial boxes. +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + btScalar bigBoxX = 7.0f; + btScalar bigBoxY = 9.0f; + btScalar bigBoxZ = 11.0f; + btScalar bigBoxMass = bigBoxX * bigBoxY * bigBoxZ; + btMatrix3x3 bitBoxInertia; + computeBoxInertia(bigBoxMass, btVector3(bigBoxX, bigBoxY, bigBoxZ), bitBoxInertia); + + btScalar smallBoxX = bigBoxX / 2.0f; + btScalar smallBoxY = bigBoxY; + btScalar smallBoxZ = bigBoxZ; + btScalar smallBoxMass = smallBoxX * smallBoxY * smallBoxZ; + btMatrix3x3 smallBoxI; + computeBoxInertia(smallBoxMass, btVector3(smallBoxX, smallBoxY, smallBoxZ), smallBoxI); + + btVector3 smallBoxOffset(smallBoxX / 2.0f, 0.0f, 0.0f); + + btMatrix3x3 smallBoxShiftedRight = smallBoxI; + applyParallelAxisTheorem(smallBoxShiftedRight, smallBoxOffset, smallBoxMass); + + btMatrix3x3 smallBoxShiftedLeft = smallBoxI; + applyParallelAxisTheorem(smallBoxShiftedLeft, -smallBoxOffset, smallBoxMass); + + btMatrix3x3 twoSmallBoxesInertia = smallBoxShiftedRight + smallBoxShiftedLeft; + + // verify bigBox same as twoSmallBoxes + btScalar error; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = bitBoxInertia[i][j] - twoSmallBoxesInertia[i][j]; + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : box inertia[" << i << "][" << j << "] off by = " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + printMatrix("expected inertia", bitBoxInertia); + printMatrix("computed inertia", twoSmallBoxesInertia); +#endif // VERBOSE_UNIT_TESTS +#endif // EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +} + +void MeshMassPropertiesTests::testTetrahedron(){ + // given the four vertices of a tetrahedron verify the analytic formula for inertia + // agrees with expected results +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + btVector3 points[4]; + points[0] = btVector3(8.33220f, -11.86875f, 0.93355f); + points[1] = btVector3(0.75523f, 5.00000f, 16.37072f); + points[2] = btVector3(52.61236f, 5.00000f, -5.38580f); + points[3] = btVector3(2.00000f, 5.00000f, 3.00000f); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + // compute volume + btScalar volume = computeTetrahedronVolume(points); + btScalar error = (volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + btVector3 centerOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + // compute inertia tensor + // (shift the points so that tetrahedron's local centerOfMass is at origin) + for (int i = 0; i < 4; ++i) { + points[i] -= centerOfMass; + } + btMatrix3x3 inertia; + computeTetrahedronInertia(volume, points, inertia); + + // verify + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", inertia); + + // when building VERBOSE you might be instrested in the results from the brute force method: + btMatrix3x3 bruteInertia; + computeTetrahedronInertiaByBruteForce(points, bruteInertia); + printMatrix("brute inertia", bruteInertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testOpenTetrahedonMesh() { + // given the simplest possible mesh (open, with one triangle) + // verify MeshMassProperties computes the right nubers +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + VectorOfPoints points; + points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); + points.push_back(btVector3(0.75523f, 5.00000f, 16.37072f)); + points.push_back(btVector3(52.61236f, 5.00000f, -5.38580f)); + points.push_back(btVector3(2.00000f, 5.00000f, 3.00000f)); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + // test as an open mesh with one triangle + VectorOfPoints shiftedPoints; + shiftedPoints.push_back(points[0] - points[0]); + shiftedPoints.push_back(points[1] - points[0]); + shiftedPoints.push_back(points[2] - points[0]); + shiftedPoints.push_back(points[3] - points[0]); + VectorOfIndices triangles; + pushTriangle(triangles, 1, 2, 3); + btVector3 expectedCenterOfMass = 0.25f * (shiftedPoints[0] + shiftedPoints[1] + shiftedPoints[2] + shiftedPoints[3]); + + // compute mass properties + MeshMassProperties mesh(shiftedPoints, triangles); + + // verify + btScalar error = (mesh._volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh._centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh._inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testClosedTetrahedronMesh() { + // given a tetrahedron as a closed mesh of four tiangles + // verify MeshMassProperties computes the right nubers +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + VectorOfPoints points; + points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); + points.push_back(btVector3(0.75523f, 5.00000f, 16.37072f)); + points.push_back(btVector3(52.61236f, 5.00000f, -5.38580f)); + points.push_back(btVector3(2.00000f, 5.00000f, 3.00000f)); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + btVector3 expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + VectorOfIndices triangles; + pushTriangle(triangles, 0, 2, 1); + pushTriangle(triangles, 0, 3, 2); + pushTriangle(triangles, 0, 1, 3); + pushTriangle(triangles, 1, 2, 3); + + // compute mass properties + MeshMassProperties mesh(points, triangles); + + // verify + btScalar error; + error = (mesh._volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh._centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "(a) tetrahedron as mesh" << std::endl; + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh._inertia); +#endif // VERBOSE_UNIT_TESTS + + // test again, but this time shift the points so that the origin is definitely OUTSIDE the mesh + btVector3 shift = points[0] + expectedCenterOfMass; + for (int i = 0; i < (int)points.size(); ++i) { + points[i] += shift; + } + expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + // compute mass properties + mesh.computeMassProperties(points, triangles); + + // verify + error = (mesh._volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh._centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "(b) shifted tetrahedron as mesh" << std::endl; + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh._inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testBoxAsMesh() { + // verify that a mesh box produces the same mass properties as the analytic box. +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + + // build a box: + // / + // y + // / + // 6-------------------------7 + // /| /| + // / | / | + // / 2----------------------/--3 + // / / / / + // | 4-------------------------5 / --x-- + // z | / | / + // | |/ |/ + // 0 ------------------------1 + + btScalar x(5.0f); + btScalar y(3.0f); + btScalar z(2.0f); + + VectorOfPoints points; + points.reserve(8); + + points.push_back(btVector3(0.0f, 0.0f, 0.0f)); + points.push_back(btVector3(x, 0.0f, 0.0f)); + points.push_back(btVector3(0.0f, y, 0.0f)); + points.push_back(btVector3(x, y, 0.0f)); + points.push_back(btVector3(0.0f, 0.0f, z)); + points.push_back(btVector3(x, 0.0f, z)); + points.push_back(btVector3(0.0f, y, z)); + points.push_back(btVector3(x, y, z)); + + VectorOfIndices triangles; + pushTriangle(triangles, 0, 1, 4); + pushTriangle(triangles, 1, 5, 4); + pushTriangle(triangles, 1, 3, 5); + pushTriangle(triangles, 3, 7, 5); + pushTriangle(triangles, 2, 0, 6); + pushTriangle(triangles, 0, 4, 6); + pushTriangle(triangles, 3, 2, 7); + pushTriangle(triangles, 2, 6, 7); + pushTriangle(triangles, 4, 5, 6); + pushTriangle(triangles, 5, 7, 6); + pushTriangle(triangles, 0, 2, 1); + pushTriangle(triangles, 2, 3, 1); + + // compute expected mass properties analytically + btVector3 expectedCenterOfMass = 0.5f * btVector3(x, y, z); + btScalar expectedVolume = x * y * z; + btMatrix3x3 expectedInertia; + computeBoxInertia(expectedVolume, btVector3(x, y, z), expectedInertia); + + // compute the mass properties using the mesh + MeshMassProperties mesh(points, triangles); + + // verify + btScalar error; + error = (mesh._volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh._centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + if (expectedInertia [i][j] == btScalar(0.0f)) { + error = mesh._inertia[i][j] - expectedInertia[i][j]; + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << " absolute"<< std::endl; + } + } else { + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; + std::cout << "expected center of mass = < " + << expectedCenterOfMass[0] << ", " + << expectedCenterOfMass[1] << ", " + << expectedCenterOfMass[2] << "> " << std::endl; + std::cout << "computed center of mass = < " + << mesh._centerOfMass[0] << ", " + << mesh._centerOfMass[1] << ", " + << mesh._centerOfMass[2] << "> " << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh._inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::runAllTests() { + testParallelAxisTheorem(); + testTetrahedron(); + testOpenTetrahedonMesh(); + testClosedTetrahedronMesh(); + testBoxAsMesh(); + //testWithCube(); +} diff --git a/tests/physics/src/MeshMassPropertiesTests.h b/tests/physics/src/MeshMassPropertiesTests.h new file mode 100644 index 0000000000..ab352bfce2 --- /dev/null +++ b/tests/physics/src/MeshMassPropertiesTests.h @@ -0,0 +1,22 @@ +// +// MeshMassPropertiesTests.h +// tests/physics/src +// +// Created by Virendra Singh on 2015.03.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_MeshMassPropertiesTests_h +#define hifi_MeshMassPropertiesTests_h +namespace MeshMassPropertiesTests{ + void testParallelAxisTheorem(); + void testTetrahedron(); + void testOpenTetrahedonMesh(); + void testClosedTetrahedronMesh(); + void testBoxAsMesh(); + void runAllTests(); +} +#endif // hifi_MeshMassPropertiesTests_h diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp index 0f35ed5002..f63925bb34 100644 --- a/tests/physics/src/main.cpp +++ b/tests/physics/src/main.cpp @@ -12,13 +12,13 @@ #include "ShapeInfoTests.h" #include "ShapeManagerTests.h" #include "BulletUtilTests.h" -#include "MeshInfoTests.h" +#include "MeshMassPropertiesTests.h" int main(int argc, char** argv) { ShapeColliderTests::runAllTests(); ShapeInfoTests::runAllTests(); ShapeManagerTests::runAllTests(); BulletUtilTests::runAllTests(); - MeshInfoTests::runAllTests(); + MeshMassPropertiesTests::runAllTests(); return 0; } diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 0ba7416b28..87338e414b 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -9,7 +9,6 @@ // #include "TextRenderer.h" -#include "MatrixStack.h" #include #include