Merge branch 'master' into plugins_pt3

This commit is contained in:
Brad Davis 2015-05-27 23:45:31 -07:00
commit e5bc0fad1e
36 changed files with 1573 additions and 888 deletions

View file

@ -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.

0
examples/blockWorld.js Normal file
View file

View file

@ -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);

View file

@ -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);
});

View file

@ -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);

View file

@ -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();

View file

@ -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");

View file

@ -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);

View file

@ -2159,9 +2159,6 @@ void Application::init() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
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);

View file

@ -147,9 +147,9 @@ public:
Q_INVOKABLE glm::vec3 getNeckPosition() const;
Q_INVOKABLE const glm::vec3& getAcceleration() const { return _acceleration; }
Q_INVOKABLE const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
Q_INVOKABLE const glm::vec3& getAngularAcceleration() const { return _angularAcceleration; }
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; }
/// Scales a world space position vector relative to the avatar position and scale

View file

@ -123,17 +123,17 @@ glm::quat AvatarMotionState::getObjectRotation() const {
}
// virtual
const glm::vec3& AvatarMotionState::getObjectLinearVelocity() const {
glm::vec3 AvatarMotionState::getObjectLinearVelocity() const {
return _avatar->getVelocity();
}
// virtual
const glm::vec3& AvatarMotionState::getObjectAngularVelocity() const {
glm::vec3 AvatarMotionState::getObjectAngularVelocity() const {
return _avatar->getAngularVelocity();
}
// virtual
const glm::vec3& AvatarMotionState::getObjectGravity() const {
glm::vec3 AvatarMotionState::getObjectGravity() const {
return _avatar->getAcceleration();
}

View file

@ -48,9 +48,9 @@ public:
virtual glm::vec3 getObjectPosition() const;
virtual glm::quat getObjectRotation() const;
virtual const glm::vec3& getObjectLinearVelocity() const;
virtual const glm::vec3& getObjectAngularVelocity() const;
virtual const glm::vec3& getObjectGravity() const;
virtual glm::vec3 getObjectLinearVelocity() const;
virtual glm::vec3 getObjectAngularVelocity() const;
virtual glm::vec3 getObjectGravity() const;
virtual const QUuid& getObjectID() const;

View file

@ -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);

View file

@ -301,7 +301,7 @@ public:
int getReceiveRate() const;
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; }
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
const glm::vec3& getTargetVelocity() const { return _targetVelocity; }
bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; }

View file

@ -113,7 +113,6 @@ void EntityTreeRenderer::init() {
connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity);
connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::addingEntity);
connect(entityTree, &EntityTree::entityScriptChanging, this, &EntityTreeRenderer::entitySciptChanging);
connect(entityTree, &EntityTree::changingEntityID, this, &EntityTreeRenderer::changingEntityID);
}
void EntityTreeRenderer::shutdown() {
@ -873,6 +872,7 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity);
connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity);
connect(this, &EntityTreeRenderer::collisionWithEntity, entityScriptingInterface, &EntityScriptingInterface::collisionWithEntity);
}
QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) {
@ -1101,20 +1101,23 @@ void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) {
}
void EntityTreeRenderer::changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID) {
if (_entityScripts.contains(oldEntityID)) {
EntityScriptDetails details = _entityScripts[oldEntityID];
_entityScripts.remove(oldEntityID);
_entityScripts[newEntityID] = details;
}
}
void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision) {
EntityItemPointer entity = entityTree->findEntityByEntityItemID(id);
if (!entity) {
return;
}
QUuid simulatorID = entity->getSimulatorID();
if (simulatorID.isNull()) {
// Can be null if it has never moved since being created or coming out of persistence.
// However, for there to be a collission, one of the two objects must be moving.
const EntityItemID& otherID = (id == collision.idA) ? collision.idB : collision.idA;
EntityItemPointer otherEntity = entityTree->findEntityByEntityItemID(otherID);
if (!otherEntity) {
return;
}
simulatorID = otherEntity->getSimulatorID();
}
if (simulatorID.isNull() || (simulatorID != myNodeID)) {
return; // Only one injector per simulation, please.
}
@ -1187,6 +1190,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
playEntityCollisionSound(myNodeID, entityTree, idB, collision);
// And now the entity scripts
emit collisionWithEntity(idA, idB, collision);
QScriptValue entityScriptA = loadEntityScript(idA);
if (entityScriptA.property("collisionWithEntity").isValid()) {
QScriptValueList args;
@ -1196,6 +1200,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
entityScriptA.property("collisionWithEntity").call(entityScriptA, args);
}
emit collisionWithEntity(idB, idA, collision);
QScriptValue entityScriptB = loadEntityScript(idB);
if (entityScriptB.property("collisionWithEntity").isValid()) {
QScriptValueList args;

View file

@ -107,11 +107,11 @@ signals:
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
public slots:
void addingEntity(const EntityItemID& entityID);
void deletingEntity(const EntityItemID& entityID);
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
void entitySciptChanging(const EntityItemID& entityID);
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);

View file

@ -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 = _position;
glm::quat saveRotation = _rotation;
// 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 = _position;
glm::quat saveRotation = _rotation;
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<quint32> 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<quint32> 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<quint64> updateDeltaCoder = encodedUpdateDelta;
quint64 updateDelta = updateDeltaCoder;
// last updated is stored as ByteCountCoded delta from lastEdited
QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint64> 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<quint64> 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<quint64> 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<NodeList>();
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
_position = savePosition;
_rotation = saveRotation;
// _velocity = saveVelocity;
// _angularVelocity = saveAngularVelocity;
// _gravity = saveGravity;
// _acceleration = saveAcceleration;
_velocity = saveVelocity;
_angularVelocity = saveAngularVelocity;
_dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES);
}
return bytesRead;
@ -948,40 +948,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;
}

View file

@ -83,6 +83,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
entity->setLastBroadcast(usecTimestampNow());
// This Node is creating a new object. If it's in motion, set this Node as the simulator.
bidForSimulationOwnership(propertiesWithSimID);
entity->setSimulatorID(propertiesWithSimID.getSimulatorID()); // and make note of it now, so we can act on it right away.
} else {
qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false;

View file

@ -121,6 +121,7 @@ public slots:
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
void canAdjustLocksChanged(bool canAdjustLocks);
void canRezChanged(bool canRez);

View file

@ -171,7 +171,6 @@ signals:
void deletingEntity(const EntityItemID& entityID);
void addingEntity(const EntityItemID& entityID);
void entityScriptChanging(const EntityItemID& entityItemID);
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
void clearingEntities();
private:

View file

@ -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

View file

@ -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),

View file

@ -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<EntityMotionState*>(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()
<< "---------------------------------------------";

View file

@ -61,9 +61,9 @@ public:
virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); }
virtual glm::quat getObjectRotation() const { return _entity->getRotation(); }
virtual const glm::vec3& getObjectLinearVelocity() const { return _entity->getVelocity(); }
virtual const glm::vec3& getObjectAngularVelocity() const { return _entity->getAngularVelocity(); }
virtual const glm::vec3& getObjectGravity() const { return _entity->getGravity(); }
virtual glm::vec3 getObjectLinearVelocity() const { return _entity->getVelocity(); }
virtual glm::vec3 getObjectAngularVelocity() const { return _entity->getAngularVelocity(); }
virtual glm::vec3 getObjectGravity() const { return _entity->getGravity(); }
virtual const QUuid& getObjectID() const { return _entity->getID(); }

View file

@ -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 <iostream>
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<Vertex> *vertices, vector<int> *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<float> MeshInfo::computeMassProperties(){
vector<float> 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;
}

View file

@ -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 <vector>
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
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<float> computeVolumeAndInertia(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const;
public:
vector<Vertex> *_vertices;
Vertex _centerOfMass;
vector<int> *_triangles;
MeshInfo(vector<Vertex> *vertices, vector<int> *triangles);
~MeshInfo();
Vertex getMeshCentroid() const;
vector<float> computeMassProperties();
};
}
#endif // hifi_MeshInfo_h

View file

@ -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 <assert.h>
#include <stdint.h>
#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);
}

View file

@ -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 <vector>
#include <stdint.h>
#include <btBulletDynamicsCommon.h>
typedef std::vector<btVector3> VectorOfPoints;
typedef std::vector<uint32_t> 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

View file

@ -112,9 +112,9 @@ public:
virtual glm::vec3 getObjectPosition() const = 0;
virtual glm::quat getObjectRotation() const = 0;
virtual const glm::vec3& getObjectLinearVelocity() const = 0;
virtual const glm::vec3& getObjectAngularVelocity() const = 0;
virtual const glm::vec3& getObjectGravity() const = 0;
virtual glm::vec3 getObjectLinearVelocity() const = 0;
virtual glm::vec3 getObjectAngularVelocity() const = 0;
virtual glm::vec3 getObjectGravity() const = 0;
virtual const QUuid& getObjectID() const = 0;

View file

@ -389,6 +389,94 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func
}
}
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) {
if (!_registeredHandlers.contains(entityID)) {
return;
}
const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
if (!handlersOnEntity.contains(eventName)) {
return;
}
QScriptValueList handlersForEvent = handlersOnEntity[eventName];
if (!handlersForEvent.isEmpty()) {
QScriptValueList args = argGenerator();
for (int i = 0; i < handlersForEvent.count(); ++i) {
handlersForEvent[i].call(QScriptValue(), args);
}
}
}
// Unregister the handlers for this eventName and entityID.
void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
if (!_registeredHandlers.contains(entityID)) {
return;
}
RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
QScriptValueList& handlersForEvent = handlersOnEntity[eventName];
// QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.
for (int i = 0; i < handlersForEvent.count(); ++i) {
if (handlersForEvent[i].equals(handler)) {
handlersForEvent.removeAt(i);
return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.
}
}
}
// Register the handler.
void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script...
// Connect up ALL the handlers to the global entities object's signals.
// (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
auto entities = DependencyManager::get<EntityScriptingInterface>();
connect(entities.data(), &EntityScriptingInterface::deletingEntity, this,
[=](const EntityItemID& entityID) {
_registeredHandlers.remove(entityID);
});
// Two common cases of event handler, differing only in argument signature.
auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&)> {
return [=](const EntityItemID& entityItemID) -> void {
generalHandler(entityItemID, eventName, [=]() -> QScriptValueList {
return QScriptValueList() << entityItemID.toScriptValue(this);
});
};
};
auto makeMouseHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&, const MouseEvent&)> {
return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void {
generalHandler(entityItemID, eventName, [=]() -> QScriptValueList {
return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this);
});
};
};
connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity"));
connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity"));
connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makeMouseHandler("mousePressOnEntity"));
connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makeMouseHandler("mouseMoveOnEntity"));
connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makeMouseHandler("mouseReleaseOnEntity"));
connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makeMouseHandler("clickDownOnEntity"));
connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makeMouseHandler("holdingClickOnEntity"));
connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makeMouseHandler("clickReleaseOnEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makeMouseHandler("hoverEnterEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity"));
connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this,
[=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) {
generalHandler(idA, "collisionWithEntity", [=]() {
return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision);
});
});
}
if (!_registeredHandlers.contains(entityID)) {
_registeredHandlers[entityID] = RegisteredEventHandlers();
}
QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName];
handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler().
}
void ScriptEngine::evaluate() {
if (_stoppingAllScripts) {
return; // bail early

View file

@ -23,6 +23,7 @@
#include <AvatarData.h>
#include <AvatarHashMap.h>
#include <LimitedNodeList.h>
#include <EntityItemID.h>
#include "AbstractControllerScriptingInterface.h"
#include "ArrayBufferClass.h"
@ -36,6 +37,8 @@ const QString NO_SCRIPT("");
const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5);
typedef QHash<QString, QScriptValueList> RegisteredEventHandlers;
class ScriptEngine : public QScriptEngine, public ScriptUser {
Q_OBJECT
public:
@ -98,6 +101,9 @@ public:
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
virtual void errorInLoadingScript(const QUrl& url);
Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
public slots:
void loadURL(const QUrl& scriptURL);
void stop();
@ -164,6 +170,8 @@ private:
ArrayBufferClass* _arrayBufferClass;
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator);
private:
static QSet<ScriptEngine*> _allKnownScriptEngines;

View file

@ -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 <iostream>
#include <iomanip>
#include <MeshInfo.h>
#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<glm::vec3> vertices = { p11, p22, p33 };
vector<int> 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<float> 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<glm::vec3> vertices = { p0, p1, p2, p3 };
vector<int> triangles = { 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 3 };
meshinfo::MeshInfo massProp(&vertices, &triangles);
vector<float> 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<glm::vec3> 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<int> 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<float> 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<glm::vec3> 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<int> 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<float> 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();
}

View file

@ -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

View file

@ -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 <iostream>
#include <string>
#include <MeshMassProperties.h>
#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();
}

View file

@ -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

View file

@ -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;
}