resolve conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2015-01-27 14:24:48 -08:00
commit 54660dd1a2
67 changed files with 1851 additions and 1288 deletions

View file

@ -6,8 +6,8 @@
* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1g
* IMPORTANT: OpenSSL 1.0.1g is critical to avoid a security vulnerability.
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
* [Bullet Physics Engine](http://bulletphysics.org) ~> 2.82
* [Soxr](http://sourceforge.net/projects/soxr/) ~> 0.1.1
* [Bullet Physics Engine](https://code.google.com/p/bullet/downloads/list) ~> 2.82
### OS Specific Build Guides
* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X.

View file

@ -1,15 +1,10 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Windows specific instructions are found in this file.
###Windows Dependencies
###Windows Specific Dependencies
* [GLEW](http://glew.sourceforge.net/) ~> 1.10.0
* [freeglut MSVC](http://www.transmissionzero.co.uk/software/freeglut-devel/) ~> 2.8.1
* [zLib](http://www.zlib.net/) ~> 1.2.8
###Visual Studio
Currently building on Windows has been tested using the following compilers:
* Visual Studio 2013
* Visual Studio 2013 Express
* (remember that you need all other dependencies listed in [BUILD.md](BUILD.md))
####Visual Studio 2013
@ -30,14 +25,15 @@ You can use the online installer or the offline installer. If you use the offlin
NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit version of libraries for interface.exe to run. The 32-bit version of the static library is the one linked by our CMake find modules.
* Download the online installer [here](http://qt-project.org/downloads)
* [Download the online installer](http://qt-project.org/downloads)
* When it asks you to select components, ONLY select the following:
* Qt > Qt 5.3.2 > **msvc2013 32-bit OpenGL**
* Download the offline installer [here](http://download.qt-project.org/official_releases/qt/5.3/5.3.2/qt-opensource-windows-x86-msvc2013_opengl-5.3.2.exe)
* [Download the offline installer](http://download.qt-project.org/official_releases/qt/5.3/5.3.2/qt-opensource-windows-x86-msvc2013_opengl-5.3.2.exe)
Once Qt is installed, you need to manually configure the following:
* Make sure the Qt runtime DLLs are loadable. You must do this before you attempt to build because some tools for the build depend on Qt. E.g., add to the PATH: `Qt\5.3.2\msvc2013_opengl\bin\`.
* Go to Control Panel > System > Advanced System Settings > Environment Variables > New ...
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.3.2\msvc2013_opengl` directory.
###External Libraries
@ -47,6 +43,9 @@ CMake will need to know where the headers and libraries for required external de
The recommended route for CMake to find the external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure:
root_lib_dir
-> bullet
-> include
-> lib
-> freeglut
-> bin
-> include

View file

@ -61,7 +61,7 @@ Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal win
This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option.
./assignment-client -n 6
./assignment-client -n 4
To test things out you'll want to run the Interface client.

View file

@ -47,7 +47,6 @@ var yawFromMouse = 0;
var pitchFromMouse = 0;
var isMouseDown = false;
var BULLET_VELOCITY = 5.0;
var MIN_THROWER_DELAY = 1000;
var MAX_THROWER_DELAY = 1000;
var LEFT_BUTTON_3 = 3;
@ -98,7 +97,7 @@ var reticle = Overlays.addOverlay("image", {
y: screenSize.y / 2 - 16,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png",
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
@ -113,6 +112,25 @@ var offButton = Overlays.addOverlay("image", {
alpha: 1
});
var platformButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 130,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/city.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var gridButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 164,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
if (showScore) {
var text = Overlays.addOverlay("text", {
x: screenSize.x / 2 - 100,
@ -127,24 +145,30 @@ if (showScore) {
});
}
function printVector(string, vector) {
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
}
var BULLET_VELOCITY = 10.0;
function shootBullet(position, velocity) {
var BULLET_SIZE = 0.07;
function shootBullet(position, velocity, grenade) {
var BULLET_SIZE = 0.10;
var BULLET_LIFETIME = 10.0;
var BULLET_GRAVITY = 0.0;
var BULLET_GRAVITY = -0.25;
var GRENADE_VELOCITY = 15.0;
var GRENADE_SIZE = 0.35;
var GRENADE_GRAVITY = -9.8;
var bVelocity = grenade ? Vec3.multiply(GRENADE_VELOCITY, Vec3.normalize(velocity)) : velocity;
var bSize = grenade ? GRENADE_SIZE : BULLET_SIZE;
var bGravity = grenade ? GRENADE_GRAVITY : BULLET_GRAVITY;
bulletID = Entities.addEntity(
{ type: "Sphere",
position: position,
dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE },
dimensions: { x: bSize, y: bSize, z: bSize },
color: { red: 255, green: 0, blue: 0 },
velocity: velocity,
velocity: bVelocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
gravity: { x: 0, y: bGravity, z: 0 },
damping: 0.01,
density: 5000,
density: 8000,
ignoreCollisions: false,
collisionsWillMove: true
});
@ -158,13 +182,15 @@ function shootBullet(position, velocity) {
}
// Kickback the arm
if (elbowKickAngle > 0.0) {
MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback);
}
rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm");
var armRotation = MyAvatar.getJointRotation("LeftForeArm");
armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE));
MyAvatar.setJointData("LeftForeArm", armRotation);
elbowKickAngle = KICKBACK_ANGLE;
}
function shootTarget() {
var TARGET_SIZE = 0.50;
var TARGET_GRAVITY = 0.0;
@ -174,7 +200,7 @@ function shootTarget() {
var DISTANCE_TO_LAUNCH_FROM = 5.0;
var ANGLE_RANGE_FOR_LAUNCH = 20.0;
var camera = Camera.getPosition();
//printVector("camera", camera);
var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 });
targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection);
var forwardVector = Quat.getFront(targetDirection);
@ -205,6 +231,78 @@ function shootTarget() {
Audio.playSound(targetLaunchSound, audioOptions);
}
function makeGrid(type, scale, size) {
var separation = scale * 2;
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
var x, y, z;
var GRID_LIFE = 60.0;
var dimensions;
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
for (z = 0; z < size; z++) {
dimensions = { x: separation/2.0 * (0.5 + Math.random()), y: separation/2.0 * (0.5 + Math.random()), z: separation/2.0 * (0.5 + Math.random()) / 4.0 };
Entities.addEntity(
{ type: type,
position: { x: pos.x + x * separation, y: pos.y + y * separation, z: pos.z + z * separation },
dimensions: dimensions,
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
velocity: { x: 0, y: 0, z: 0 },
gravity: { x: 0, y: 0, z: 0 },
lifetime: GRID_LIFE,
rotation: Camera.getOrientation(),
damping: 0.1,
density: 100.0,
collisionsWillMove: true });
}
}
}
}
function makePlatform(gravity, scale, size) {
var separation = scale * 2;
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
pos.y -= separation * size;
var x, y, z;
var TARGET_LIFE = 60.0;
var INITIAL_GAP = 0.5;
var dimensions;
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
for (z = 0; z < size; z++) {
dimensions = { x: separation/2.0, y: separation, z: separation/2.0 };
Entities.addEntity(
{ type: "Box",
position: { x: pos.x - (separation * size / 2.0) + x * separation,
y: pos.y + y * (separation + INITIAL_GAP),
z: pos.z - (separation * size / 2.0) + z * separation },
dimensions: dimensions,
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
velocity: { x: 0, y: 0, z: 0 },
gravity: { x: 0, y: gravity, z: 0 },
lifetime: TARGET_LIFE,
damping: 0.1,
density: 100.0,
collisionsWillMove: true });
}
}
}
// Make a floor for this stuff to fall onto
Entities.addEntity({
type: "Box",
position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z },
dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size },
color: { red: 128, green: 128, blue: 128 },
lifetime: TARGET_LIFE
});
}
function entityCollisionWithEntity(entity1, entity2, collision) {
if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) &&
@ -233,7 +331,9 @@ function keyPressEvent(event) {
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
Script.setTimeout(shootTarget, time);
} else if ((event.text == ".") || (event.text == "SPACE")) {
shootFromMouse();
shootFromMouse(false);
} else if (event.text == ",") {
shootFromMouse(true);
} else if (event.text == "r") {
playLoadSound();
} else if (event.text == "s") {
@ -241,7 +341,6 @@ function keyPressEvent(event) {
Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm"));
Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm"));
Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand"));
}
}
@ -287,18 +386,7 @@ function update(deltaTime) {
if (targetID && !targetID.isKnownID) {
targetID = Entities.identifyEntity(targetID);
}
// Check for mouseLook movement, update rotation
// rotate body yaw for yaw received from mouse
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } ));
//MyAvatar.orientation = newOrientation;
yawFromMouse = 0;
// apply pitch from mouse
var newPitch = MyAvatar.headPitch + pitchFromMouse;
//MyAvatar.headPitch = newPitch;
pitchFromMouse = 0;
if (activeControllers == 0) {
if (Controller.getNumberOfSpatialControls() > 0) {
activeControllers = Controller.getNumberOfSpatialControls();
@ -372,19 +460,19 @@ function update(deltaTime) {
var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector));
shootBullet(position, velocity);
shootBullet(position, velocity, false);
}
}
}
}
function shootFromMouse() {
function shootFromMouse(grenade) {
var DISTANCE_FROM_CAMERA = 1.0;
var camera = Camera.getPosition();
var forwardVector = Quat.getFront(Camera.getOrientation());
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA));
var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY);
shootBullet(newPosition, velocity);
shootBullet(newPosition, velocity, grenade);
}
function mouseReleaseEvent(event) {
@ -392,21 +480,24 @@ function mouseReleaseEvent(event) {
isMouseDown = false;
}
function mouseMoveEvent(event) {
if (isMouseDown) {
var MOUSE_YAW_SCALE = -0.25;
var MOUSE_PITCH_SCALE = -12.5;
var FIXED_MOUSE_TIMESTEP = 0.016;
yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
lastX = event.x;
lastY = event.y;
}
function mousePressEvent(event) {
var clickedText = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == offButton) {
Script.stop();
} else if (clickedOverlay == platformButton) {
var platformSize = 3;
makePlatform(-9.8, 1.0, platformSize);
} else if (clickedOverlay == gridButton) {
makeGrid("Box", 1.0, 3);
}
}
function scriptEnding() {
Overlays.deleteOverlay(reticle);
Overlays.deleteOverlay(offButton);
Overlays.deleteOverlay(platformButton);
Overlays.deleteOverlay(gridButton);
Overlays.deleteOverlay(pointer[0]);
Overlays.deleteOverlay(pointer[1]);
Overlays.deleteOverlay(text);
@ -418,7 +509,7 @@ Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(update);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.keyPressEvent.connect(keyPressEvent);

View file

@ -14,13 +14,9 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("libraries/entityPropertyDialogBox.js");
Script.include("../../libraries/entityPropertyDialogBox.js");
var entityPropertyDialogBox = EntityPropertyDialogBox;
var LASER_WIDTH = 4;
var LASER_COLOR = { red: 255, green: 0, blue: 0 };
var LASER_LENGTH_FACTOR = 500;
var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;
var allowLargeModels = false;
@ -32,7 +28,44 @@ var RIGHT = 1;
var jointList = MyAvatar.getJointNames();
var mode = 0;
var STICKS = 0;
var MAPPED = 1;
var mode = STICKS;
var LASER_WIDTH = 4;
var LASER_COLOR = [{ red: 200, green: 150, blue: 50 }, // STICKS
{ red: 50, green: 150, blue: 200 }]; // MAPPED
var LASER_LENGTH_FACTOR = 500;
var lastAccurateIntersection = null;
var accurateIntersections = 0;
var totalIntersections = 0;
var inaccurateInARow = 0;
var maxInaccurateInARow = 0;
function getRayIntersection(pickRay) { // pickRay : { origin : {xyz}, direction : {xyz} }
if (lastAccurateIntersection === null) {
lastAccurateIntersection = Entities.findRayIntersectionBlocking(pickRay);
} else {
var intersection = Entities.findRayIntersection(pickRay);
if (intersection.accurate) {
lastAccurateIntersection = intersection;
accurateIntersections++;
maxInaccurateInARow = (maxInaccurateInARow > inaccurateInARow) ? maxInaccurateInARow : inaccurateInARow;
inaccurateInARow = 0;
} else {
inaccurateInARow++;
}
totalIntersections++;
}
return lastAccurateIntersection;
}
function printIntersectionsStats() {
var ratio = accurateIntersections / totalIntersections;
print("Out of " + totalIntersections + " intersections, " + accurateIntersections + " where accurate. (" + ratio * 100 +"%)");
print("Worst case was " + maxInaccurateInARow + " inaccurate intersections in a row.");
}
function controller(wichSide) {
this.side = wichSide;
@ -42,10 +75,10 @@ function controller(wichSide) {
this.bumper = 6 * wichSide + 5;
this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm);
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
this.palmPosition = this.oldPalmPosition;
this.oldTipPosition = Controller.getSpatialControlPosition(this.tip);
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
this.tipPosition = this.oldTipPosition;
this.oldUp = Controller.getSpatialControlNormal(this.palm);
this.up = this.oldUp;
@ -75,13 +108,13 @@ function controller(wichSide) {
this.positionAtGrab;
this.rotationAtGrab;
this.modelPositionAtGrab;
this.rotationAtGrab;
this.modelRotationAtGrab;
this.jointsIntersectingFromStart = [];
this.laser = Overlays.addOverlay("line3d", {
start: { x: 0, y: 0, z: 0 },
end: { x: 0, y: 0, z: 0 },
color: LASER_COLOR,
color: LASER_COLOR[mode],
alpha: 1,
visible: false,
lineWidth: LASER_WIDTH,
@ -132,7 +165,7 @@ function controller(wichSide) {
this.positionAtGrab = this.palmPosition;
this.rotationAtGrab = this.rotation;
this.modelPositionAtGrab = properties.position;
this.rotationAtGrab = properties.rotation;
this.modelRotationAtGrab = properties.rotation;
this.jointsIntersectingFromStart = [];
for (var i = 0; i < jointList.length; i++) {
var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition);
@ -245,9 +278,9 @@ function controller(wichSide) {
var inverseRotation = Quat.inverse(MyAvatar.orientation);
var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale);
var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
var distance = Vec3.length(direction);
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / distance);
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale));
var endPosition = Vec3.sum(startPosition, direction);
Overlays.editOverlay(this.laser, {
@ -267,17 +300,16 @@ function controller(wichSide) {
start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)),
end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale))
});
this.showLaser(!this.grabbing || mode == 0);
this.showLaser(!this.grabbing || mode == STICKS);
if (this.glowedIntersectingModel.isKnownID) {
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 });
this.glowedIntersectingModel.isKnownID = false;
}
if (!this.grabbing) {
var intersection = Entities.findRayIntersection({
origin: this.palmPosition,
direction: this.front
});
var intersection = getRayIntersection({ origin: this.palmPosition,
direction: this.front
});
var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0;
@ -304,17 +336,16 @@ function controller(wichSide) {
if (this.grabbing) {
if (!this.entityID.isKnownID) {
print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
this.entityID = Entities.findRayIntersection({
origin: this.palmPosition,
direction: this.front
}).entityID;
this.entityID = getRayIntersection({ origin: this.palmPosition,
direction: this.front
}).entityID;
print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
}
var newPosition;
var newRotation;
switch (mode) {
case 0:
case STICKS:
newPosition = Vec3.sum(this.palmPosition,
Vec3.multiply(this.front, this.x));
newPosition = Vec3.sum(newPosition,
@ -328,7 +359,7 @@ function controller(wichSide) {
newRotation = Quat.multiply(newRotation,
this.oldModelRotation);
break;
case 1:
case MAPPED:
var forward = Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 });
var d = Vec3.dot(forward, MyAvatar.position);
@ -350,8 +381,9 @@ function controller(wichSide) {
newRotation = Quat.multiply(this.rotation,
Quat.inverse(this.rotationAtGrab));
newRotation = Quat.multiply(newRotation, newRotation);
newRotation = Quat.multiply(newRotation,
this.rotationAtGrab);
this.modelRotationAtGrab);
break;
}
Entities.editEntity(this.entityID, {
@ -397,15 +429,13 @@ function controller(wichSide) {
var bumperValue = Controller.isButtonPressed(this.bumper);
if (bumperValue && !this.bumperValue) {
if (mode == 0) {
mode = 1;
Overlays.editOverlay(leftController.laser, { color: { red: 0, green: 0, blue: 255 } });
Overlays.editOverlay(rightController.laser, { color: { red: 0, green: 0, blue: 255 } });
} else {
mode = 0;
Overlays.editOverlay(leftController.laser, { color: { red: 255, green: 0, blue: 0 } });
Overlays.editOverlay(rightController.laser, { color: { red: 255, green: 0, blue: 0 } });
if (mode === STICKS) {
mode = MAPPED;
} else if (mode === MAPPED) {
mode = STICKS;
}
Overlays.editOverlay(leftController.laser, { color: LASER_COLOR[mode] });
Overlays.editOverlay(rightController.laser, { color: LASER_COLOR[mode] });
}
this.bumperValue = bumperValue;
@ -475,10 +505,10 @@ function controller(wichSide) {
Vec3.print("Looking at: ", this.palmPosition);
var pickRay = { origin: this.palmPosition,
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) };
var foundIntersection = Entities.findRayIntersection(pickRay);
var foundIntersection = getRayIntersection(pickRay);
if(!foundIntersection.accurate) {
print("No accurate intersection");
if(!foundIntersection.intersects) {
print("No intersection");
return;
}
newModel = foundIntersection.entityID;
@ -526,7 +556,7 @@ function moveEntities() {
switch (mode) {
case 0:
case STICKS:
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
@ -545,7 +575,7 @@ function moveEntities() {
newPosition = Vec3.sum(middle,
Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio));
break;
case 1:
case MAPPED:
var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition));
var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition));
@ -566,11 +596,11 @@ function moveEntities() {
leftController.positionAtGrab = leftController.palmPosition;
leftController.rotationAtGrab = leftController.rotation;
leftController.modelPositionAtGrab = leftController.oldModelPosition;
leftController.rotationAtGrab = rotation;
leftController.modelRotationAtGrab = rotation;
rightController.positionAtGrab = rightController.palmPosition;
rightController.rotationAtGrab = rightController.rotation;
rightController.modelPositionAtGrab = rightController.oldModelPosition;
rightController.rotationAtGrab = rotation;
rightController.modelRotationAtGrab = rotation;
break;
}
Entities.editEntity(leftController.entityID, {
@ -628,43 +658,56 @@ var glowedEntityID = { id: -1, isKnownID: false };
// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
// added it.
var ROOT_MENU = "Edit";
var ITEM_BEFORE = "Physics";
var MENU_SEPARATOR = "Models";
var EDIT_PROPERTIES = "Edit Properties...";
var INTERSECTION_STATS = "Print Intersection Stats";
var DELETE = "Delete";
var LARGE_MODELS = "Allow Selecting of Large Models";
var SMALL_MODELS = "Allow Selecting of Small Models";
var LIGHTS = "Allow Selecting of Lights";
var modelMenuAddedDelete = false;
var originalLightsArePickable = Entities.getLightsArePickable();
function setupModelMenus() {
print("setupModelMenus()");
// adj our menuitems
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...",
shortcutKeyEvent: { text: "`" }, afterItem: "Models" });
if (!Menu.menuItemExists("Edit", "Delete")) {
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: MENU_SEPARATOR, isSeparator: true, beforeItem: ITEM_BEFORE });
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: EDIT_PROPERTIES,
shortcutKeyEvent: { text: "`" }, afterItem: MENU_SEPARATOR });
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: INTERSECTION_STATS, afterItem: MENU_SEPARATOR });
if (!Menu.menuItemExists(ROOT_MENU, DELETE)) {
print("no delete... adding ours");
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete",
shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" });
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: DELETE,
shortcutKeyEvent: { text: "backspace" }, afterItem: MENU_SEPARATOR });
modelMenuAddedDelete = true;
} else {
print("delete exists... don't add ours");
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
afterItem: "Allow Selecting of Large Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L",
afterItem: "Allow Selecting of Small Models", isCheckable: true });
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LARGE_MODELS, shortcutKey: "CTRL+META+L",
afterItem: DELETE, isCheckable: true });
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: SMALL_MODELS, shortcutKey: "CTRL+META+S",
afterItem: LARGE_MODELS, isCheckable: true });
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LIGHTS, shortcutKey: "CTRL+SHIFT+META+L",
afterItem: SMALL_MODELS, isCheckable: true });
Entities.setLightsArePickable(false);
}
function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Edit Properties...");
Menu.removeSeparator(ROOT_MENU, MENU_SEPARATOR);
Menu.removeMenuItem(ROOT_MENU, EDIT_PROPERTIES);
Menu.removeMenuItem(ROOT_MENU, INTERSECTION_STATS);
if (modelMenuAddedDelete) {
// delete our menuitems
Menu.removeMenuItem("Edit", "Delete");
Menu.removeMenuItem(ROOT_MENU, DELETE);
}
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Lights");
Menu.removeMenuItem(ROOT_MENU, LARGE_MODELS);
Menu.removeMenuItem(ROOT_MENU, SMALL_MODELS);
Menu.removeMenuItem(ROOT_MENU, LIGHTS);
}
@ -688,13 +731,13 @@ function showPropertiesForm(editModelID) {
Menu.menuItemEvent.connect(function (menuItem) {
print("menuItemEvent() in JS... menuItem=" + menuItem);
if (menuItem == "Allow Selecting of Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
} else if (menuItem == "Allow Selecting of Large Models") {
allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models");
} else if (menuItem == "Allow Selecting of Lights") {
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
} else if (menuItem == "Delete") {
if (menuItem == SMALL_MODELS) {
allowSmallModels = Menu.isOptionChecked(SMALL_MODELS);
} else if (menuItem == LARGE_MODELS) {
allowLargeModels = Menu.isOptionChecked(LARGE_MODELS);
} else if (menuItem == LIGHTS) {
Entities.setLightsArePickable(Menu.isOptionChecked(LIGHTS));
} else if (menuItem == DELETE) {
if (leftController.grabbing) {
print(" Delete Entity.... leftController.entityID="+ leftController.entityID);
Entities.deleteEntity(leftController.entityID);
@ -712,7 +755,7 @@ Menu.menuItemEvent.connect(function (menuItem) {
} else {
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Edit Properties...") {
} else if (menuItem == EDIT_PROPERTIES) {
editModelID = -1;
if (leftController.grabbing) {
print(" Edit Properties.... leftController.entityID="+ leftController.entityID);
@ -727,16 +770,18 @@ Menu.menuItemEvent.connect(function (menuItem) {
print(" Edit Properties.... about to edit properties...");
showPropertiesForm(editModelID);
}
} else if (menuItem == INTERSECTION_STATS) {
printIntersectionsStats();
}
});
Controller.keyReleaseEvent.connect(function (event) {
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
if (event.text == "`") {
handeMenuEvent("Edit Properties...");
handeMenuEvent(EDIT_PROPERTIES);
}
if (event.text == "BACKSPACE") {
handeMenuEvent("Delete");
handeMenuEvent(DELETE);
}
});

View file

@ -0,0 +1,172 @@
// PaddleBall.js
//
// Created by Philip Rosedale on January 21, 2015
// Copyright 2014 High Fidelity, Inc.
//
// Move your hand with the hydra controller, and hit the ball with the paddle.
// Click 'X' button to turn off this script.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var BALL_SIZE = 0.08;
var PADDLE_SIZE = 0.20;
var PADDLE_THICKNESS = 0.06;
var PADDLE_COLOR = { red: 184, green: 134, blue: 11 };
var BALL_COLOR = { red: 255, green: 0, blue: 0 };
var LINE_COLOR = { red: 255, green: 255, blue: 0 };
var PADDLE_OFFSET = { x: 0.05, y: 0.0, z: 0.0 };
var GRAVITY = 0.0;
var SPRING_FORCE = 15.0;
var lastSoundTime = 0;
var gameOn = false;
var leftHanded = false;
var controllerID;
if (leftHanded) {
controllerID = 1;
} else {
controllerID = 3;
}
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
hitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav");
var screenSize = Controller.getViewportDimensions();
var offButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 96,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var ball, paddle, paddleModel, line;
function createEntities() {
ball = Entities.addEntity(
{ type: "Sphere",
position: Controller.getSpatialControlPosition(controllerID),
dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE },
color: BALL_COLOR,
gravity: { x: 0, y: GRAVITY, z: 0 },
ignoreCollisions: false,
damping: 0.50,
collisionsWillMove: true });
paddle = Entities.addEntity(
{ type: "Box",
position: Controller.getSpatialControlPosition(controllerID),
dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 0.80 },
color: PADDLE_COLOR,
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
damping: 0.10,
visible: false,
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
collisionsWillMove: false });
modelURL = "http://public.highfidelity.io/models/attachments/pong_paddle.fbx";
paddleModel = Entities.addEntity(
{ type: "Model",
position: Vec3.sum(Controller.getSpatialControlPosition(controllerID), PADDLE_OFFSET),
dimensions: { x: PADDLE_SIZE * 1.5, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 1.25 },
color: PADDLE_COLOR,
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: true,
modelURL: modelURL,
damping: 0.10,
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
collisionsWillMove: false });
line = Overlays.addOverlay("line3d", {
start: { x: 0, y: 0, z: 0 },
end: { x: 0, y: 0, z: 0 },
color: LINE_COLOR,
alpha: 1,
visible: true,
lineWidth: 2 });
}
function deleteEntities() {
Entities.deleteEntity(ball);
Entities.deleteEntity(paddle);
Entities.deleteEntity(paddleModel);
Overlays.deleteOverlay(line);
}
function update(deltaTime) {
var palmPosition = Controller.getSpatialControlPosition(controllerID);
var controllerActive = (Vec3.length(palmPosition) > 0);
if (!gameOn && controllerActive) {
createEntities();
gameOn = true;
} else if (gameOn && !controllerActive) {
deleteEntities();
gameOn = false;
}
if (!gameOn || !controllerActive) {
return;
}
if (!paddle.isKnownID) {
paddle = Entities.identifyEntity(paddle);
}
if (!ball.isKnownID) {
ball = Entities.identifyEntity(ball);
} else {
var props = Entities.getEntityProperties(ball);
var spring = Vec3.subtract(palmPosition, props.position);
var paddleWorldOrientation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID));
var springLength = Vec3.length(spring);
spring = Vec3.normalize(spring);
var ballVelocity = Vec3.sum(props.velocity, Vec3.multiply(springLength * SPRING_FORCE * deltaTime, spring));
Entities.editEntity(ball, { velocity: ballVelocity });
Overlays.editOverlay(line, { start: props.position, end: palmPosition });
Entities.editEntity(paddle, { position: palmPosition,
velocity: Controller.getSpatialControlVelocity(controllerID),
rotation: paddleWorldOrientation });
Entities.editEntity(paddleModel, { position: Vec3.sum(palmPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_OFFSET)),
velocity: Controller.getSpatialControlVelocity(controllerID),
rotation: paddleWorldOrientation });
}
}
function entityCollisionWithEntity(entity1, entity2, collision) {
if ((entity1.id == ball.id) || (entity2.id ==ball.id)) {
var props1 = Entities.getEntityProperties(entity1);
var props2 = Entities.getEntityProperties(entity2);
var dVel = Vec3.length(Vec3.subtract(props1.velocity, props2.velocity));
var currentTime = new Date().getTime();
var MIN_MSECS_BETWEEN_BOUNCE_SOUNDS = 100;
var MIN_VELOCITY_FOR_SOUND_IMPACT = 0.25;
if ((dVel > MIN_VELOCITY_FOR_SOUND_IMPACT) && (currentTime - lastSoundTime) > MIN_MSECS_BETWEEN_BOUNCE_SOUNDS) {
Audio.playSound(hitSound, { position: props1.position, volume: Math.min(dVel, 1.0) });
lastSoundTime = new Date().getTime();
}
}
}
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == offButton) {
Script.stop();
}
}
function scriptEnding() {
if (gameOn) {
deleteEntities();
}
Overlays.deleteOverlay(offButton);
}
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
Controller.mousePressEvent.connect(mousePressEvent);
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(update);

View file

@ -60,11 +60,9 @@ Script.update.connect(function(deltaTime) {
}
if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){
MyAvatar.stopAnimation(leftHandAnimation);
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
}
if ((rightFrame != lastRightFrame) && rightHandAnimation.length) {
MyAvatar.stopAnimation(rightHandAnimation);
MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame);
}

View file

@ -1,6 +1,7 @@
// Pool Table
var tableParts = [];
var balls = [];
var cueBall;
var LENGTH = 2.84;
var WIDTH = 1.42;
@ -13,6 +14,8 @@ var HOLE_SIZE = BALL_SIZE;
var DROP_HEIGHT = BALL_SIZE * 3.0;
var GRAVITY = -9.8;
var BALL_GAP = 0.001;
var tableCenter;
var cuePosition;
var startStroke = 0;
@ -23,7 +26,7 @@ var reticle = Overlays.addOverlay("image", {
y: screenSize.y / 2 - 16,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png",
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
@ -102,7 +105,7 @@ function makeBalls(pos) {
{ red: 128, green: 128, blue: 128}]; // Gray
// Object balls
var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z };
var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z };
for (var row = 1; row <= 5; row++) {
ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE);
for (var spot = 0; spot < row; spot++) {
@ -113,23 +116,26 @@ function makeBalls(pos) {
color: colors[balls.length],
gravity: { x: 0, y: GRAVITY, z: 0 },
ignoreCollisions: false,
damping: 0.40,
damping: 0.50,
collisionsWillMove: true }));
ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE;
}
ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE;
}
// Cue Ball
ballPosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z };
balls.push(Entities.addEntity(
cuePosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z };
cueBall = Entities.addEntity(
{ type: "Sphere",
position: ballPosition,
position: cuePosition,
dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE },
color: { red: 255, green: 255, blue: 255 },
gravity: { x: 0, y: GRAVITY, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 },
velocity: {x: 0, y: 0, z: 0 },
ignoreCollisions: false,
damping: 0.40,
collisionsWillMove: true }));
damping: 0.50,
collisionsWillMove: true });
}
function shootCue(velocity) {
@ -140,19 +146,23 @@ function shootCue(velocity) {
var velocity = Vec3.multiply(forwardVector, velocity);
var BULLET_LIFETIME = 3.0;
var BULLET_GRAVITY = 0.0;
var SHOOTER_COLOR = { red: 255, green: 0, blue: 0 };
var SHOOTER_SIZE = BALL_SIZE / 1.5 * SCALE;
bulletID = Entities.addEntity(
{ type: "Sphere",
position: cuePosition,
dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE },
color: { red: 255, green: 255, blue: 255 },
dimensions: { x: SHOOTER_SIZE, y: SHOOTER_SIZE, z: SHOOTER_SIZE },
color: SHOOTER_COLOR,
velocity: velocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
damping: 0.10,
density: 1000,
density: 8000,
ignoreCollisions: false,
collisionsWillMove: true
});
print("Shot, velocity = " + velocity);
}
function keyReleaseEvent(event) {
@ -185,9 +195,25 @@ function cleanup() {
Entities.deleteEntity(balls[i]);
}
Overlays.deleteOverlay(reticle);
Entities.deleteEntity(cueBall);
}
var tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation())));
function update(deltaTime) {
if (!cueBall.isKnownID) {
cueBall = Entities.identifyEntity(cueBall);
} else {
// Check if cue ball has fallen off table, re-drop if so
var cueProperties = Entities.getEntityProperties(cueBall);
if (cueProperties.position.y < tableCenter.y) {
// Replace the cueball
Entities.editEntity(cueBall, { position: cuePosition } );
}
}
}
tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation())));
makeTable(tableCenter);
makeBalls(tableCenter);
@ -195,3 +221,4 @@ makeBalls(tableCenter);
Script.scriptEnding.connect(cleanup);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Script.update.connect(update);

View file

@ -8,7 +8,7 @@ body {
background-color: rgb(76, 76, 76);
color: rgb(204, 204, 204);
font-family: Arial;
font-size: 11px;
font-size: 8.25pt;
-webkit-touch-callout: none;
-webkit-user-select: none;
@ -31,25 +31,25 @@ body {
.color-box {
display: inline-block;
width: 20px;
height: 20px;
border: 1px solid black;
margin: 2px;
width: 15pt;
height: 15pt;
border: 0.75pt solid black;
margin: 1.5pt;
cursor: pointer;
}
.color-box.highlight {
width: 18px;
height: 18px;
border: 2px solid black;
width: 13.5pt;
height: 13.5pt;
border: 1.5pt solid black;
}
.section-header {
background: #AAA;
border-bottom: 1px solid #CCC;
border-bottom: 0.75pt solid #CCC;
background-color: #333333;
color: #999;
padding: 4px;
padding: 3pt;
}
.section-header label {
@ -61,7 +61,7 @@ body {
.property-section {
display: block;
margin: 10 10;
height: 30px;
height: 22.5pt;
}
.property-section label {
@ -73,7 +73,7 @@ body {
}
.grid-section {
border-top: 1px solid #DDD;
border-top: 0.75pt solid #DDD;
background-color: #efefef;
}
@ -81,8 +81,8 @@ input[type=button] {
cursor: pointer;
background-color: #608e96;
border-color: #608e96;
border-radius: 5px;
padding: 5px 10px;
border-radius: 3.75pt;
padding: 3.75pt 7.5pt;
border: 0;
color: #fff;
font-weight: bold;
@ -90,8 +90,8 @@ input[type=button] {
textarea, input {
margin: 0;
padding: 2px;
border: 1px solid #999;
padding: 1.5pt;
border: 0.75pt solid #999;
background-color: #eee;
}
@ -112,13 +112,13 @@ input.coord {
table#entity-table {
border-collapse: collapse;
font-family: Sans-Serif;
font-size: 10px;
font-size: 7.5pt;
width: 100%;
}
#entity-table tr {
cursor: pointer;
border-bottom: 1px solid rgb(63, 63, 63)
border-bottom: 0.75pt solid rgb(63, 63, 63)
}
#entity-table tr.selected {
@ -128,15 +128,15 @@ table#entity-table {
#entity-table th {
background-color: #333;
color: #fff;
border: 0px black solid;
border: 0pt black solid;
text-align: left;
word-wrap: nowrap;
white-space: nowrap;
}
#entity-table td {
font-size: 11px;
border: 0px black solid;
font-size: 8.25pt;
border: 0pt black solid;
word-wrap: nowrap;
white-space: nowrap;
text-overflow: ellipsis;
@ -148,21 +148,21 @@ table#entity-table {
}
th#entity-type {
width: 60px;
width: 33.75pt;
}
div.input-area {
display: inline-block;
font-size: 10px;
font-size: 7.5pt;
}
input {
}
#type {
font-size: 14px;
font-size: 10.5pt;
}
#type label {
@ -173,22 +173,22 @@ input {
background-color: rgb(102, 102, 102);
color: rgb(204, 204, 204);
border: none;
font-size: 10px;
font-size: 7.5pt;
}
#properties-list input[type=button] {
cursor: pointer;
background-color: rgb(51, 102, 102);
border-color: #608e96;
border-radius: 5px;
padding: 5px 10px;
border-radius: 3.75pt;
padding: 3.75pt 7.5pt;
border: 0;
color: rgb(204, 204, 204);
}
#properties-list .property {
padding: 8px 8px;
border-top: 1px solid rgb(63, 63, 63);
padding: 6pt 6pt;
border-top: 0.75pt solid rgb(63, 63, 63);
min-height: 1em;
}
@ -203,11 +203,11 @@ table#properties-list {
}
#properties-list > div {
margin: 4px 0;
margin: 3pt 0;
}
#properties-list {
border-bottom: 1px solid #e5e5e5;
border-bottom: 0.75pt solid #e5e5e5;
}
#properties-list .label {
@ -221,11 +221,11 @@ table#properties-list {
}
#properties-list .value > div{
padding: 4px 0;
padding: 3pt 0;
}
col#col-label {
width: 130px;
width: 97.5pt;
}
div.outer {

View file

@ -158,6 +158,11 @@ function handleModes() {
avatarOrientation.w != MyAvatar.orientation.w)) {
newMode = noMode;
}
if (mode == noMode && newMode != noMode && Camera.mode == "independent") {
newMode = noMode;
}
// if leaving noMode
if (mode == noMode && newMode != noMode) {
saveCameraState();

View file

@ -80,7 +80,8 @@ CameraManager = function() {
that.lastMousePosition = { x: 0, y: 0 };
that.enable = function() {
if (that.enabled) return;
if (Camera.mode == "independent" || that.enabled) return;
that.enabled = true;
that.mode = MODE_INACTIVE;

View file

@ -10,7 +10,7 @@
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
Script.include("libraries/toolBars.js");
Script.include("../../libraries/toolBars.js");
var recordingFile = "recording.rec";

View file

@ -18,6 +18,10 @@ function setupMenus() {
}
if (!Menu.menuExists("Developer > Entities")) {
Menu.addMenu("Developer > Entities");
// NOTE: these menu items aren't currently working. I've temporarily removed them. Will add them back once we
// rewire these to work
/*
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Bounds", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Triangles", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Element Bounds", isCheckable: true, isChecked: false });
@ -26,9 +30,26 @@ function setupMenus() {
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Attempt Render Entities as Scene", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Disable Light Entities", isCheckable: true, isChecked: false });
*/
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't send collision updates to server", isCheckable: true, isChecked: false });
}
}
Menu.menuItemEvent.connect(function (menuItem) {
print("menuItemEvent() in JS... menuItem=" + menuItem);
if (menuItem == "Don't send collision updates to server") {
var dontSendUpdates = Menu.isOptionChecked("Don't send collision updates to server");
print(" dontSendUpdates... checked=" + dontSendUpdates);
Entities.setSendPhysicsUpdates(!dontSendUpdates);
}
});
setupMenus();
// register our scriptEnding callback
Script.scriptEnding.connect(scriptEnding);
function scriptEnding() {
Menu.removeMenu("Developer > Entities");
}

View file

@ -4,6 +4,6 @@ set(TARGET_NAME ice-server)
setup_hifi_project(Network)
# link the shared hifi libraries
link_hifi_libraries(networking shared)
link_hifi_libraries(embedded-webserver networking shared)
include_dependency_includes()

View file

@ -20,14 +20,18 @@
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
const quint16 ICE_SERVER_MONITORING_PORT = 40110;
IceServer::IceServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_id(QUuid::createUuid()),
_serverSocket(),
_activePeers()
_activePeers(),
_httpManager(ICE_SERVER_MONITORING_PORT, QString("%1/web/").arg(QCoreApplication::applicationDirPath()), this)
{
// start the ice-server socket
qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT;
qDebug() << "monitoring http endpoint is listening on " << ICE_SERVER_MONITORING_PORT;
_serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT);
// call our process datagrams slot when the UDP socket has packets ready
@ -165,3 +169,17 @@ void IceServer::clearInactivePeers() {
}
}
}
bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
//
// We need an HTTP handler in order to monitor the health of the ice server
// The correct functioning of the ICE server will be determined by its HTTP availability,
//
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (url.path() == "/status") {
connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size()));
}
}
return true;
}

View file

@ -12,17 +12,21 @@
#ifndef hifi_IceServer_h
#define hifi_IceServer_h
#include <qcoreapplication.h>
#include <qsharedpointer.h>
#include <qudpsocket.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QSharedPointer>
#include <QUdpSocket>
#include <NetworkPeer.h>
#include <HTTPConnection.h>
#include <HTTPManager.h>
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
class IceServer : public QCoreApplication {
class IceServer : public QCoreApplication, public HTTPRequestHandler {
Q_OBJECT
public:
IceServer(int argc, char* argv[]);
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
private slots:
void processDatagrams();
void clearInactivePeers();
@ -34,6 +38,7 @@ private:
QUdpSocket _serverSocket;
NetworkPeerHash _activePeers;
QHash<QUuid, QSet<QUuid> > _currentConnections;
HTTPManager _httpManager;
};
#endif // hifi_IceServer_h

View file

@ -192,7 +192,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_justStarted(true),
_physicsEngine(glm::vec3(0.0f)),
_entities(true, this, this),
_entityCollisionSystem(),
_entityClipboardRenderer(false, this, this),
_entityClipboard(),
_viewFrustum(),
@ -1696,17 +1695,16 @@ void Application::init() {
_entities.init();
_entities.setViewFrustum(getViewFrustum());
EntityTree* entityTree = _entities.getTree();
_entityCollisionSystem.init(&_entityEditSender, entityTree, &_avatarManager);
entityTree->setSimulation(&_entityCollisionSystem);
EntityTree* tree = _entities.getTree();
_physicsEngine.setEntityTree(tree);
tree->setSimulation(&_physicsEngine);
_physicsEngine.init(&_entityEditSender);
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity);
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
&_entities, &EntityTreeRenderer::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
@ -1730,10 +1728,6 @@ void Application::init() {
// save settings when avatar changes
connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings);
EntityTree* tree = _entities.getTree();
_physicsEngine.setEntityTree(tree);
tree->setSimulation(&_physicsEngine);
_physicsEngine.init(&_entityEditSender);
// make sure our texture cache knows about window size changes
DependencyManager::get<TextureCache>()->associateWithWidget(glCanvas.data());
@ -2054,9 +2048,6 @@ void Application::update(float deltaTime) {
// NOTE: the _entities.update() call below will wait for lock
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
_entities.update(); // update the models...
// The _entityCollisionSystem.updateCollisions() call below merely tries for lock,
// and on failure it skips collision detection.
_entityCollisionSystem.updateCollisions(); // collide the entities...
}
{

View file

@ -25,7 +25,6 @@
#include <AbstractScriptingServicesInterface.h>
#include <AbstractViewStateInterface.h>
#include <EntityCollisionSystem.h>
#include <EntityEditPacketSender.h>
#include <EntityTreeRenderer.h>
#include <GeometryCache.h>
@ -468,7 +467,6 @@ private:
PhysicsEngine _physicsEngine;
EntityTreeRenderer _entities;
EntityCollisionSystem _entityCollisionSystem;
EntityTreeRenderer _entityClipboardRenderer;
EntityTree _entityClipboard;

View file

@ -1427,6 +1427,8 @@ public:
void setColorMaterial(const StackArray::Entry& entry) { color = entry.color; material = entry.material; }
void mix(const EdgeCrossing& first, const EdgeCrossing& second, float t);
VoxelPoint createPoint(int clampedX, int clampedZ, float step) const;
};
void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, float t) {
@ -1437,6 +1439,16 @@ void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, fl
material = (t < 0.5f) ? first.material : second.material;
}
VoxelPoint EdgeCrossing::createPoint(int clampedX, int clampedZ, float step) const {
VoxelPoint voxelPoint = { glm::vec3(clampedX + point.x, point.y, clampedZ + point.z) * step,
{ (quint8)qRed(color), (quint8)qGreen(color), (quint8)qBlue(color) },
{ (char)(normal.x * numeric_limits<qint8>::max()), (char)(normal.y * numeric_limits<qint8>::max()),
(char)(normal.z * numeric_limits<qint8>::max()) },
{ (quint8)material, 0, 0, 0 },
{ numeric_limits<quint8>::max(), 0, 0, 0 } };
return voxelPoint;
}
const int MAX_NORMALS_PER_VERTEX = 4;
class NormalIndex {
@ -1498,6 +1510,84 @@ const NormalIndex& IndexVector::get(int y) const {
return (relative >= 0 && relative < size()) ? at(relative) : invalidIndex;
}
static inline glm::vec3 getNormal(const QVector<VoxelPoint>& vertices, const NormalIndex& i0,
const NormalIndex& i1, const NormalIndex& i2, const NormalIndex& i3) {
// check both triangles in case one is degenerate
const glm::vec3& v0 = vertices.at(i0.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(i1.indices[0]).vertex - v0, vertices.at(i2.indices[0]).vertex - v0);
if (glm::length(normal) > EPSILON) {
return normal;
}
return glm::cross(vertices.at(i2.indices[0]).vertex - v0, vertices.at(i3.indices[0]).vertex - v0);
}
static inline void appendTriangle(const EdgeCrossing& e0, const EdgeCrossing& e1, const EdgeCrossing& e2,
int clampedX, int clampedZ, float step, QVector<VoxelPoint>& vertices, QVector<int>& indices,
QMultiHash<VoxelCoord, int>& quadIndices) {
int firstIndex = vertices.size();
vertices.append(e0.createPoint(clampedX, clampedZ, step));
vertices.append(e1.createPoint(clampedX, clampedZ, step));
vertices.append(e2.createPoint(clampedX, clampedZ, step));
indices.append(firstIndex);
indices.append(firstIndex + 1);
indices.append(firstIndex + 2);
indices.append(firstIndex + 2);
int minimumY = qMin((int)e0.point.y, qMin((int)e1.point.y, (int)e2.point.y));
int maximumY = qMax((int)e0.point.y, qMax((int)e1.point.y, (int)e2.point.y));
for (int y = minimumY; y <= maximumY; y++) {
quadIndices.insert(qRgb(clampedX, y, clampedZ), firstIndex);
}
}
const int CORNER_COUNT = 4;
static StackArray::Entry getEntry(const StackArray* lineSrc, int stackWidth, int y, float heightfieldHeight,
EdgeCrossing cornerCrossings[CORNER_COUNT], int cornerIndex) {
int offsetX = (cornerIndex & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (cornerIndex & Y_MAXIMUM_FLAG) ? 1 : 0;
const StackArray& src = lineSrc[offsetZ * stackWidth + offsetX];
int count = src.getEntryCount();
if (count > 0) {
int relative = y - src.getPosition();
if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f)) {
return src.getEntry(y, heightfieldHeight);
}
}
const EdgeCrossing& cornerCrossing = cornerCrossings[cornerIndex];
if (cornerCrossing.point.y == 0.0f) {
return src.getEntry(y, heightfieldHeight);
}
StackArray::Entry entry;
bool set = false;
if (cornerCrossing.point.y >= y) {
entry.color = cornerCrossing.color;
entry.material = cornerCrossing.material;
set = true;
entry.setHermiteY(cornerCrossing.normal, glm::clamp(cornerCrossing.point.y - y, 0.0f, 1.0f));
} else {
entry.material = entry.color = 0;
}
if (!(cornerIndex & X_MAXIMUM_FLAG)) {
const EdgeCrossing& nextCornerCrossingX = cornerCrossings[cornerIndex | X_MAXIMUM_FLAG];
if (nextCornerCrossingX.point.y != 0.0f && (nextCornerCrossingX.point.y >= y) != set) {
float t = glm::clamp((y - cornerCrossing.point.y) /
(nextCornerCrossingX.point.y - cornerCrossing.point.y), 0.0f, 1.0f);
entry.setHermiteX(glm::normalize(glm::mix(cornerCrossing.normal, nextCornerCrossingX.normal, t)), t);
}
}
if (!(cornerIndex & Y_MAXIMUM_FLAG)) {
const EdgeCrossing& nextCornerCrossingZ = cornerCrossings[cornerIndex | Y_MAXIMUM_FLAG];
if (nextCornerCrossingZ.point.y != 0.0f && (nextCornerCrossingZ.point.y >= y) != set) {
float t = glm::clamp((y - cornerCrossing.point.y) /
(nextCornerCrossingZ.point.y - cornerCrossing.point.y), 0.0f, 1.0f);
entry.setHermiteZ(glm::normalize(glm::mix(cornerCrossing.normal, nextCornerCrossingZ.normal, t)), t);
}
}
return entry;
}
void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const glm::vec3& translation,
const glm::quat& rotation, const glm::vec3& scale, bool cursor) {
if (!node->getHeight()) {
@ -1706,7 +1796,6 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
const int LOWER_RIGHT_CORNER = 8;
const int NO_CORNERS = 0;
const int ALL_CORNERS = UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER;
const int CORNER_COUNT = 4;
const int NEXT_CORNERS[] = { 1, 3, 0, 2 };
int corners = NO_CORNERS;
if (heightfieldHeight != 0.0f) {
@ -1772,6 +1861,15 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
minimumY = qMin(minimumY, cornerMinimumY);
maximumY = qMax(maximumY, cornerMaximumY);
if (corners == (LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER)) {
appendTriangle(cornerCrossings[1], cornerCrossings[0], cornerCrossings[2],
clampedX, clampedZ, step, vertices, indices, quadIndices);
} else if (corners == (UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER)) {
appendTriangle(cornerCrossings[2], cornerCrossings[3], cornerCrossings[1],
clampedX, clampedZ, step, vertices, indices, quadIndices);
}
}
int position = minimumY;
int count = maximumY - minimumY + 1;
@ -1781,7 +1879,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
indicesZ[x].position = position;
indicesZ[x].resize(count);
for (int y = position, end = position + count; y < end; y++) {
const StackArray::Entry& entry = lineSrc->getEntry(y, heightfieldHeight);
StackArray::Entry entry = getEntry(lineSrc, stackWidth, y, heightfieldHeight, cornerCrossings, 0);
if (displayHermite && x != 0 && z != 0 && !lineSrc->isEmpty() && y >= lineSrc->getPosition()) {
glm::vec3 normal;
if (entry.hermiteX != 0) {
@ -1846,41 +1944,38 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
if (!(corners & (1 << i))) {
continue;
}
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
float heightValue = *height * voxelScale;
if (heightValue >= y && heightValue < y + 1) {
const EdgeCrossing& cornerCrossing = cornerCrossings[i];
if (cornerCrossing.point.y >= y && cornerCrossing.point.y < y + 1) {
crossedCorners |= (1 << i);
}
}
switch (crossedCorners) {
case UPPER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
case UPPER_LEFT_CORNER | LOWER_RIGHT_CORNER:
crossings[crossingCount++] = cornerCrossings[0];
crossings[crossingCount - 1].point.y -= y;
break;
case UPPER_RIGHT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
crossings[crossingCount++] = cornerCrossings[1];
crossings[crossingCount - 1].point.y -= y;
break;
case LOWER_LEFT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
crossings[crossingCount++] = cornerCrossings[2];
crossings[crossingCount - 1].point.y -= y;
break;
case LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
crossings[crossingCount++] = cornerCrossings[3];
crossings[crossingCount - 1].point.y -= y;
break;
@ -1890,30 +1985,24 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
if (!(corners & (1 << i))) {
continue;
}
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
float heightValue = *height * voxelScale;
int nextIndex = NEXT_CORNERS[i];
if (!(corners & (1 << nextIndex))) {
continue;
}
int nextOffsetX = (nextIndex & X_MAXIMUM_FLAG) ? 1 : 0;
int nextOffsetZ = (nextIndex & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* nextHeight = heightLineSrc + nextOffsetZ * width + nextOffsetX;
float nextHeightValue = *nextHeight * voxelScale;
float divisor = (nextHeightValue - heightValue);
const EdgeCrossing& cornerCrossing = cornerCrossings[i];
const EdgeCrossing& nextCornerCrossing = cornerCrossings[nextIndex];
float divisor = (nextCornerCrossing.point.y - cornerCrossing.point.y);
if (divisor == 0.0f) {
continue;
}
float t1 = (y - heightValue) / divisor;
float t2 = (y + 1 - heightValue) / divisor;
float t1 = (y - cornerCrossing.point.y) / divisor;
float t2 = (y + 1 - cornerCrossing.point.y) / divisor;
if (t1 >= 0.0f && t1 <= 1.0f) {
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t1);
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t1);
crossings[crossingCount - 1].point.y -= y;
}
if (t2 >= 0.0f && t2 <= 1.0f) {
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t2);
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t2);
crossings[crossingCount - 1].point.y -= y;
}
}
@ -1924,10 +2013,13 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
// the terrifying conditional code that follows checks each cube edge for a crossing, gathering
// its properties (color, material, normal) if one is present; as before, boundary edges are excluded
if (crossingCount == 0) {
const StackArray::Entry& nextEntryY = lineSrc->getEntry(y + 1, heightfieldHeight);
StackArray::Entry nextEntryY = getEntry(lineSrc, stackWidth, y + 1,
heightfieldHeight, cornerCrossings, 0);
if (middleX) {
const StackArray::Entry& nextEntryX = lineSrc[1].getEntry(y, nextHeightfieldHeightX);
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
StackArray::Entry nextEntryX = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightX,
cornerCrossings, 1);
StackArray::Entry nextEntryXY = getEntry(lineSrc, stackWidth, y + 1, nextHeightfieldHeightX,
cornerCrossings, 1);
if (alpha0 != alpha1) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f);
@ -1944,12 +2036,12 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
}
if (middleZ) {
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y,
nextHeightfieldHeightZ);
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
const StackArray::Entry& nextEntryXYZ = lineSrc[stackWidth + 1].getEntry(
y + 1, nextHeightfieldHeightXZ);
StackArray::Entry nextEntryZ = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightZ,
cornerCrossings, 2);
StackArray::Entry nextEntryXZ = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightXZ,
cornerCrossings, 3);
StackArray::Entry nextEntryXYZ = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightXZ, cornerCrossings, 3);
if (alpha1 != alpha5) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal));
@ -1957,8 +2049,8 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
if (alpha3 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1,
nextHeightfieldHeightX);
StackArray::Entry nextEntryXY = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightX, cornerCrossings, 1);
crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY);
}
@ -1969,15 +2061,15 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
if (alpha5 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
StackArray::Entry nextEntryXZ = getEntry(lineSrc, stackWidth, y,
nextHeightfieldHeightXZ, cornerCrossings, 3);
crossing.point = glm::vec3(1.0f, nextEntryXZ.getHermiteY(crossing.normal), 1.0f);
crossing.setColorMaterial(alpha5 == 0 ? nextEntryXYZ : nextEntryXZ);
}
if (alpha6 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(
y + 1, nextHeightfieldHeightZ);
StackArray::Entry nextEntryYZ = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightZ, cornerCrossings, 2);
crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f);
crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ);
}
@ -1989,9 +2081,10 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry);
}
if (middleZ) {
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y, nextHeightfieldHeightZ);
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(y + 1,
nextHeightfieldHeightZ);
StackArray::Entry nextEntryZ = getEntry(lineSrc, stackWidth, y,
nextHeightfieldHeightZ, cornerCrossings, 2);
StackArray::Entry nextEntryYZ = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightZ, cornerCrossings, 2);
if (alpha0 != alpha4) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
@ -2174,10 +2267,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ - 1), indices.size());
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
}
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first,
vertices.at(index3.indices[0]).vertex - first);
glm::vec3 normal = getNormal(vertices, index, index1, index2, index3);
if (alpha0 == 0) { // quad faces negative x
indices.append(index3.getClosestIndex(normal = -normal, vertices));
indices.append(index2.getClosestIndex(normal, vertices));
@ -2206,10 +2296,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
if (reclampedZ > 0) {
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
}
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(index3.indices[0]).vertex - first,
vertices.at(index1.indices[0]).vertex - first);
glm::vec3 normal = getNormal(vertices, index, index3, index2, index1);
if (alpha0 == 0) { // quad faces negative y
indices.append(index3.getClosestIndex(normal, vertices));
indices.append(index2.getClosestIndex(normal, vertices));
@ -2235,10 +2322,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ), indices.size());
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first,
vertices.at(index3.indices[0]).vertex - first);
glm::vec3 normal = getNormal(vertices, index, index1, index2, index3);
if (alpha0 == 0) { // quad faces negative z
indices.append(index1.getClosestIndex(normal, vertices));
indices.append(index2.getClosestIndex(normal, vertices));

View file

@ -171,10 +171,6 @@ void Hand::renderHandTargets(bool isMine) {
glm::vec3 tip = palm.getTipPosition();
glm::vec3 root = palm.getPosition();
//Scale the positions based on avatar scale
myAvatar->scaleVectorRelativeToPosition(tip);
myAvatar->scaleVectorRelativeToPosition(root);
Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS);
// Render sphere at palm/finger root
glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS;

View file

@ -822,6 +822,12 @@ HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QStrin
_radius->setSingleStep(0.01);
_radius->setMaximum(FLT_MAX);
_radius->setValue(5.0);
_form->addRow("Granularity:", _granularity = new QDoubleSpinBox());
_granularity->setMinimum(-FLT_MAX);
_granularity->setMaximum(FLT_MAX);
_granularity->setPrefix("2^");
_granularity->setValue(8.0);
}
bool HeightfieldBrushTool::appliesTo(const AttributePointer& attribute) const {
@ -851,7 +857,7 @@ bool HeightfieldBrushTool::eventFilter(QObject* watched, QEvent* event) {
if (event->type() == QEvent::Wheel) {
float angle = static_cast<QWheelEvent*>(event)->angleDelta().y();
const float ANGLE_SCALE = 1.0f / 1000.0f;
_radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE));
_radius->setValue(_radius->value() * pow(2.0f, angle * ANGLE_SCALE));
return true;
} else if (event->type() == QEvent::MouseButtonPress && _positionValid) {
@ -881,7 +887,7 @@ QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) {
const int ERASE_MODE_INDEX = 2;
return QVariant::fromValue(PaintHeightfieldHeightEdit(_position, _radius->value(),
alternate ? -_height->value() : _height->value(), _mode->currentIndex() == SET_MODE_INDEX,
_mode->currentIndex() == ERASE_MODE_INDEX));
_mode->currentIndex() == ERASE_MODE_INDEX, pow(2.0f, _granularity->value())));
}
MaterialControl::MaterialControl(QWidget* widget, QFormLayout* form, bool clearable) :
@ -956,10 +962,11 @@ QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) {
sphere->setScale(_radius->value());
if (alternate) {
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
SharedObjectPointer(), QColor(0, 0, 0, 0), true));
SharedObjectPointer(), QColor(0, 0, 0, 0), true, false, pow(2.0f, _granularity->value())));
} else {
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
_materialControl->getMaterial(), _materialControl->getColor(), true));
_materialControl->getMaterial(), _materialControl->getColor(),
true, false, pow(2.0f, _granularity->value())));
}
}
@ -974,10 +981,11 @@ QVariant HeightfieldSculptBrushTool::createEdit(bool alternate) {
sphere->setScale(_radius->value());
if (alternate) {
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
SharedObjectPointer(), QColor(0, 0, 0, 0)));
SharedObjectPointer(), QColor(0, 0, 0, 0), false, false, pow(2.0f, _granularity->value())));
} else {
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
_materialControl->getMaterial(), _materialControl->getColor()));
_materialControl->getMaterial(), _materialControl->getColor(),
false, false, pow(2.0f, _granularity->value())));
}
}
@ -992,13 +1000,14 @@ HeightfieldFillBrushTool::HeightfieldFillBrushTool(MetavoxelEditor* editor) :
QVariant HeightfieldFillBrushTool::createEdit(bool alternate) {
const int FILL_MODE_INDEX = 0;
if (_mode->currentIndex() == FILL_MODE_INDEX) {
return QVariant::fromValue(FillHeightfieldHeightEdit(_position, _radius->value()));
return QVariant::fromValue(FillHeightfieldHeightEdit(_position, _radius->value(),
pow(2.0f, _granularity->value())));
}
Sphere* sphere = new Sphere();
sphere->setTranslation(_position);
sphere->setScale(_radius->value());
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
SharedObjectPointer(), QColor(), false, true));
SharedObjectPointer(), QColor(), false, true, pow(2.0f, _granularity->value())));
}
HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor) :
@ -1017,6 +1026,12 @@ HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor)
_snapToGrid->setChecked(true);
_materialControl = new MaterialControl(this, form, true);
form->addRow("Granularity:", _granularity = new QDoubleSpinBox());
_granularity->setMinimum(-FLT_MAX);
_granularity->setMaximum(FLT_MAX);
_granularity->setPrefix("2^");
_granularity->setValue(8.0);
}
bool HeightfieldMaterialBoxTool::appliesTo(const AttributePointer& attribute) const {
@ -1039,7 +1054,7 @@ void HeightfieldMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm:
cuboid->setAspectY(vector.y / vector.x);
cuboid->setAspectZ(vector.z / vector.x);
MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(cuboid),
_materialControl->getMaterial(), _materialControl->getColor())) };
_materialControl->getMaterial(), _materialControl->getColor(), false, false, pow(2.0f, _granularity->value()))) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
@ -1056,6 +1071,12 @@ HeightfieldMaterialSpannerTool::HeightfieldMaterialSpannerTool(MetavoxelEditor*
_materialControl = new MaterialControl(this, form, true);
form->addRow("Granularity:", _granularity = new QDoubleSpinBox());
_granularity->setMinimum(-FLT_MAX);
_granularity->setMaximum(FLT_MAX);
_granularity->setPrefix("2^");
_granularity->setValue(8.0);
QPushButton* place = new QPushButton("Set");
layout()->addWidget(place);
connect(place, &QPushButton::clicked, this, &HeightfieldMaterialSpannerTool::place);
@ -1076,7 +1097,7 @@ QColor HeightfieldMaterialSpannerTool::getColor() {
void HeightfieldMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) {
static_cast<Spanner*>(spanner.data())->setWillBeVoxelized(true);
MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(spanner,
_materialControl->getMaterial(), _materialControl->getColor())) };
_materialControl->getMaterial(), _materialControl->getColor(), false, false, pow(2.0f, _granularity->value()))) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}

View file

@ -326,6 +326,7 @@ protected:
QFormLayout* _form;
QDoubleSpinBox* _radius;
QDoubleSpinBox* _granularity;
glm::vec3 _position;
bool _positionValid;
@ -448,6 +449,7 @@ private:
QCheckBox* _snapToGrid;
MaterialControl* _materialControl;
QDoubleSpinBox* _granularity;
};
/// Allows setting heightfield materials by placing a spanner.
@ -470,6 +472,7 @@ private:
SharedObjectEditor* _spannerEditor;
MaterialControl* _materialControl;
QDoubleSpinBox* _granularity;
};
#endif // hifi_MetavoxelEditor_h

View file

@ -22,88 +22,62 @@
#include "ImageOverlay.h"
ImageOverlay::ImageOverlay() :
_textureID(0),
_imageURL(),
_renderImage(false),
_textureBound(false),
_wantClipFromImage(false)
{
_isLoaded = false;
}
ImageOverlay::ImageOverlay(const ImageOverlay* imageOverlay) :
Overlay2D(imageOverlay),
_texture(imageOverlay->_texture),
_imageURL(imageOverlay->_imageURL),
_textureImage(imageOverlay->_textureImage),
_textureID(0),
_fromImage(),
_fromImage(imageOverlay->_fromImage),
_renderImage(imageOverlay->_renderImage),
_textureBound(false),
_wantClipFromImage(false)
_wantClipFromImage(imageOverlay->_wantClipFromImage)
{
}
ImageOverlay::~ImageOverlay() {
if (_parent && _textureID) {
// do we need to call this?
//_parent->deleteTexture(_textureID);
}
}
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void ImageOverlay::setImageURL(const QUrl& url) {
_imageURL = url;
_isLoaded = false;
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &ImageOverlay::replyFinished);
}
void ImageOverlay::replyFinished() {
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
// replace our byte array with the downloaded data
QByteArray rawData = reply->readAll();
_textureImage.loadFromData(rawData);
_renderImage = true;
_isLoaded = true;
reply->deleteLater();
if (url.isEmpty()) {
_isLoaded = true;
_renderImage = false;
_texture.clear();
} else {
_isLoaded = false;
_renderImage = true;
}
}
void ImageOverlay::render(RenderArgs* args) {
if (!_visible || !_isLoaded) {
return; // do nothing if we're not visible
if (!_isLoaded && _renderImage) {
_isLoaded = true;
_texture = DependencyManager::get<TextureCache>()->getTexture(_imageURL);
}
if (_renderImage && !_textureBound) {
_textureID = _parent->bindTexture(_textureImage);
_textureBound = true;
// If we are not visible or loaded, return. If we are trying to render an
// image but the texture hasn't loaded, return.
if (!_visible || !_isLoaded || (_renderImage && !_texture->isLoaded())) {
return;
}
if (_renderImage) {
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, _textureID);
glBindTexture(GL_TEXTURE_2D, _texture->getID());
}
const float MAX_COLOR = 255.0f;
xColor color = getColor();
float alpha = getAlpha();
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
float imageWidth = _textureImage.width();
float imageHeight = _textureImage.height();
QRect fromImage;
if (_wantClipFromImage) {
fromImage = _fromImage;
} else {
fromImage.setX(0);
fromImage.setY(0);
fromImage.setWidth(imageWidth);
fromImage.setHeight(imageHeight);
}
float x = fromImage.x() / imageWidth;
float y = fromImage.y() / imageHeight;
float w = fromImage.width() / imageWidth; // ?? is this what we want? not sure
float h = fromImage.height() / imageHeight;
int left = _bounds.left();
int right = _bounds.right() + 1;
int top = _bounds.top();
@ -111,10 +85,35 @@ void ImageOverlay::render(RenderArgs* args) {
glm::vec2 topLeft(left, top);
glm::vec2 bottomRight(right, bottom);
glm::vec2 texCoordTopLeft(x, 1.0f - y);
glm::vec2 texCoordBottomRight(x + w, 1.0f - (y + h));
if (_renderImage) {
float imageWidth = _texture->getWidth();
float imageHeight = _texture->getHeight();
QRect fromImage;
if (_wantClipFromImage) {
float scaleX = imageWidth / _texture->getOriginalWidth();
float scaleY = imageHeight / _texture->getOriginalHeight();
fromImage.setX(scaleX * _fromImage.x());
fromImage.setY(scaleY * _fromImage.y());
fromImage.setWidth(scaleX * _fromImage.width());
fromImage.setHeight(scaleY * _fromImage.height());
} else {
fromImage.setX(0);
fromImage.setY(0);
fromImage.setWidth(imageWidth);
fromImage.setHeight(imageHeight);
}
float x = fromImage.x() / imageWidth;
float y = fromImage.y() / imageHeight;
float w = fromImage.width() / imageWidth; // ?? is this what we want? not sure
float h = fromImage.height() / imageHeight;
glm::vec2 texCoordTopLeft(x, y);
glm::vec2 texCoordBottomRight(x + w, y + h);
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight);
} else {
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight);
@ -153,7 +152,7 @@ void ImageOverlay::setProperties(const QScriptValue& properties) {
subImageRect.setHeight(oldSubImageRect.height());
}
setClipFromSource(subImageRect);
}
}
QScriptValue imageURL = properties.property("imageURL");
if (imageURL.isValid()) {

View file

@ -24,6 +24,7 @@
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include <TextureCache.h>
#include "Overlay.h"
#include "Overlay2D.h"
@ -49,18 +50,14 @@ public:
virtual ImageOverlay* createClone() const;
private slots:
void replyFinished(); // we actually want to hide this...
private:
QUrl _imageURL;
QImage _textureImage;
GLuint _textureID;
NetworkTexturePointer _texture;
QRect _fromImage; // where from in the image to sample
bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle
bool _textureBound; // has the texture been bound
bool _wantClipFromImage;
};

View file

@ -70,7 +70,7 @@
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>-1</pointsize>
<pointsize>16</pointsize>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
@ -78,7 +78,7 @@
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 16px;
font: bold 16pt;
</string>
</property>
<property name="text">
@ -192,7 +192,7 @@ font: bold 16px;
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font: 14px; color: #5f5f5f; margin: 2px;</string>
<string notr="true">font: 14pt; color: #5f5f5f; margin: 2px;</string>
</property>
<property name="text">
<string>There are no scripts running.</string>
@ -245,8 +245,8 @@ font: bold 16px;
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>20</height>
<width>328</width>
<height>18</height>
</rect>
</property>
<property name="sizePolicy">
@ -259,7 +259,7 @@ font: bold 16px;
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">font-size: 14px;</string>
<string notr="true">font-size: 14pt;</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
@ -301,7 +301,7 @@ font: bold 16px;
<item>
<widget class="QLabel" name="tipLabel">
<property name="styleSheet">
<string notr="true">font: 14px; color: #5f5f5f; margin: 2px;</string>
<string notr="true">font: 14pt; color: #5f5f5f; margin: 2px;</string>
</property>
<property name="text">
<string>Tip</string>
@ -370,7 +370,7 @@ font: bold 16px;
<widget class="QLabel" name="label">
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 16px;</string>
font: bold 16pt;</string>
</property>
<property name="text">
<string>Load Scripts</string>

View file

@ -125,7 +125,9 @@ void AudioClient::audioMixerKilled() {
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
QAudioDeviceInfo result;
#ifdef WIN32
// Temporarily enable audio device selection in Windows again to test how it behaves now
//#ifdef WIN32
#if FALSE
// NOTE
// this is a workaround for a windows only QtBug https://bugreports.qt-project.org/browse/QTBUG-16117
// static QAudioDeviceInfo objects get deallocated when QList<QAudioDevieInfo> objects go out of scope

View file

@ -27,7 +27,7 @@ HandData::HandData(AvatarData* owningAvatar) :
}
glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const {
return glm::inverse(getBaseOrientation()) * worldVector;
return glm::inverse(getBaseOrientation()) * worldVector / getBaseScale();
}
PalmData& HandData::addNewPalm() {
@ -108,15 +108,19 @@ glm::quat HandData::getBaseOrientation() const {
glm::vec3 HandData::getBasePosition() const {
return _owningAvatarData->getPosition();
}
float HandData::getBaseScale() const {
return _owningAvatarData->getTargetScale();
}
glm::vec3 PalmData::getFingerDirection() const {
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f);
return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION);
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION));
}
glm::vec3 PalmData::getNormal() const {
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f);
return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION);
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION));
}

View file

@ -36,11 +36,11 @@ public:
// position conversion
glm::vec3 localToWorldPosition(const glm::vec3& localPosition) {
return getBasePosition() + getBaseOrientation() * localPosition;
return getBasePosition() + getBaseOrientation() * localPosition * getBaseScale();
}
glm::vec3 localToWorldDirection(const glm::vec3& localVector) {
return getBaseOrientation() * localVector;
return getBaseOrientation() * localVector * getBaseScale();
}
glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const;
@ -71,6 +71,7 @@ protected:
glm::quat getBaseOrientation() const;
glm::vec3 getBasePosition() const;
float getBaseScale() const;
private:
// privatize copy ctor and assignment operator so copies of this object cannot be made

View file

@ -101,3 +101,12 @@ void BoxEntityItem::computeShapeInfo(ShapeInfo& info) const {
info.setBox(halfExtents);
}
void BoxEntityItem::debugDump() const {
quint64 now = usecTimestampNow();
qDebug() << " BOX EntityItem id:" << getEntityItemID() << "---------------------------------------------";
qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
qDebug() << " position:" << debugTreeVector(_position);
qDebug() << " dimensions:" << debugTreeVector(_dimensions);
qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now);
}

View file

@ -53,6 +53,8 @@ public:
void computeShapeInfo(ShapeInfo& info) const;
virtual void debugDump() const;
protected:
rgbColor _color;
};

View file

@ -1,302 +0,0 @@
//
// EntityCollisionSystem.cpp
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 9/23/14.
// Copyright 2013-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 <algorithm>
#include <AbstractAudioInterface.h>
#include <AvatarData.h>
#include <CollisionInfo.h>
#include <HeadData.h>
#include <HandData.h>
#include <PerfStat.h>
#include <SphereShape.h>
#include "EntityCollisionSystem.h"
#include "EntityEditPacketSender.h"
#include "EntityItem.h"
#include "EntityTreeElement.h"
#include "EntityTree.h"
const int MAX_COLLISIONS_PER_Entity = 16;
EntityCollisionSystem::EntityCollisionSystem()
: SimpleEntitySimulation(),
_packetSender(NULL),
_avatars(NULL),
_collisions(MAX_COLLISIONS_PER_Entity) {
}
void EntityCollisionSystem::init(EntityEditPacketSender* packetSender,
EntityTree* entities, AvatarHashMap* avatars) {
assert(entities);
setEntityTree(entities);
_packetSender = packetSender;
_avatars = avatars;
}
EntityCollisionSystem::~EntityCollisionSystem() {
}
void EntityCollisionSystem::updateCollisions() {
PerformanceTimer perfTimer("collisions");
assert(_entityTree);
// update all Entities
if (_entityTree->tryLockForWrite()) {
foreach (EntityItem* entity, _movingEntities) {
checkEntity(entity);
}
_entityTree->unlock();
}
}
void EntityCollisionSystem::checkEntity(EntityItem* entity) {
updateCollisionWithEntities(entity);
updateCollisionWithAvatars(entity);
}
void EntityCollisionSystem::emitGlobalEntityCollisionWithEntity(EntityItem* entityA,
EntityItem* entityB, const Collision& collision) {
EntityItemID idA = entityA->getEntityItemID();
EntityItemID idB = entityB->getEntityItemID();
emit entityCollisionWithEntity(idA, idB, collision);
}
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
if (entityA->getIgnoreForCollisions()) {
return; // bail early if this entity is to be ignored...
}
// don't collide entities with unknown IDs,
if (!entityA->isKnownID()) {
return;
}
glm::vec3 penetration;
EntityItem* entityB = NULL;
const int MAX_COLLISIONS_PER_ENTITY = 32;
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
bool shapeCollisionsAccurate = false;
bool shapeCollisions = _entityTree->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
collisions, Octree::NoLock, &shapeCollisionsAccurate);
if (shapeCollisions) {
for(int i = 0; i < collisions.size(); i++) {
CollisionInfo* collision = collisions[i];
penetration = collision->_penetration;
entityB = static_cast<EntityItem*>(collision->_extraData);
// The collision _extraData should be a valid entity, but if for some reason
// it's NULL then continue with a warning.
if (!entityB) {
qDebug() << "UNEXPECTED - we have a collision with missing _extraData. Something went wrong down below!";
continue; // skip this loop pass if the entity is NULL
}
// don't collide entities with unknown IDs,
if (!entityB->isKnownID()) {
continue; // skip this loop pass if the entity has an unknown ID
}
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
// Even if the Entities overlap... when the Entities are already moving appart
// we don't want to count this as a collision.
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
bool fullyEnclosedCollision = glm::length(penetrationInTreeUnits) > entityA->getLargestDimension();
bool wantToMoveA = entityA->getCollisionsWillMove();
bool wantToMoveB = entityB->getCollisionsWillMove();
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
// only do collisions if the entities are moving toward each other and one or the other
// of the entities are movable from collisions
bool doCollisions = !fullyEnclosedCollision && movingTowardEachOther && (wantToMoveA || wantToMoveB);
if (doCollisions) {
quint64 now = usecTimestampNow();
glm::vec3 axis = glm::normalize(penetration);
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
float massA = entityA->computeMass();
float massB = entityB->computeMass();
float totalMass = massA + massB;
float massRatioA = (2.0f * massB / totalMass);
float massRatioB = (2.0f * massA / totalMass);
// in the event that one of our entities is non-moving, then fix up these ratios
if (wantToMoveA && !wantToMoveB) {
massRatioA = 2.0f;
massRatioB = 0.0f;
}
if (!wantToMoveA && wantToMoveB) {
massRatioA = 0.0f;
massRatioB = 2.0f;
}
// unless the entity is configured to not be moved by collision, calculate it's new position
// and velocity and apply it
if (wantToMoveA) {
// handle Entity A
glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * massRatioA;
glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
EntityItemProperties propertiesA = entityA->getProperties();
EntityItemID idA(entityA->getID());
propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
propertiesA.setLastEdited(now);
// NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation,
// thereby waking up static non-moving entities.
_entityTree->updateEntity(entityA, propertiesA);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
}
// unless the entity is configured to not be moved by collision, calculate it's new position
// and velocity and apply it
if (wantToMoveB) {
glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * massRatioB;
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
EntityItemProperties propertiesB = entityB->getProperties();
EntityItemID idB(entityB->getID());
propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
propertiesB.setLastEdited(now);
// NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation,
// thereby waking up static non-moving entities.
_entityTree->updateEntity(entityB, propertiesB);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
}
// NOTE: Do this after updating the entities so that the callback can delete the entities if they want to
Collision collision;
collision.penetration = penetration;
collision.contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
}
}
}
}
void EntityCollisionSystem::updateCollisionWithAvatars(EntityItem* entity) {
// Entities that are in hand, don't collide with avatars
if (!_avatars) {
return;
}
if (entity->getIgnoreForCollisions() || !entity->getCollisionsWillMove()) {
return; // bail early if this entity is to be ignored or wont move
}
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
float radius = entity->getRadius() * (float)(TREE_SCALE);
const float ELASTICITY = 0.9f;
const float DAMPING = 0.1f;
glm::vec3 penetration;
_collisions.clear();
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
AvatarData* avatar = avatarPointer.data();
float totalRadius = avatar->getBoundingRadius() + radius;
glm::vec3 relativePosition = center - avatar->getPosition();
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
continue;
}
if (avatar->findSphereCollisions(center, radius, _collisions)) {
int numCollisions = _collisions.size();
for (int i = 0; i < numCollisions; ++i) {
CollisionInfo* collision = _collisions.getCollision(i);
collision->_damping = DAMPING;
collision->_elasticity = ELASTICITY;
collision->_addedVelocity /= (float)(TREE_SCALE);
glm::vec3 relativeVelocity = collision->_addedVelocity - entity->getVelocity();
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.0f) {
// only collide when Entity and collision point are moving toward each other
// (doing this prevents some "collision snagging" when Entity penetrates the object)
collision->_penetration /= (float)(TREE_SCALE);
applyHardCollision(entity, *collision);
}
}
}
}
}
void EntityCollisionSystem::applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo) {
// don't collide entities with unknown IDs,
if (!entity->isKnownID()) {
return;
}
// HALTING_* params are determined using expected acceleration of gravity over some timescale.
// This is a HACK for entities that bounce in a 1.0 gravitational field and should eventually be made more universal.
const float HALTING_ENTITY_PERIOD = 0.0167f; // ~1/60th of a second
const float HALTING_ENTITY_SPEED = 9.8 * HALTING_ENTITY_PERIOD / (float)(TREE_SCALE);
//
// Update the entity in response to a hard collision. Position will be reset exactly
// to outside the colliding surface. Velocity will be modified according to elasticity.
//
// if elasticity = 0.0, collision is inelastic (vel normal to collision is lost)
// if elasticity = 1.0, collision is 100% elastic.
//
glm::vec3 position = entity->getPosition();
glm::vec3 velocity = entity->getVelocity();
const float EPSILON = 0.0f;
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity;
float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration);
if (velocityDotPenetration < EPSILON) {
// entity is moving into collision surface
//
// TODO: do something smarter here by comparing the mass of the entity vs that of the other thing
// (other's mass could be stored in the Collision Info). The smaller mass should surrender more
// position offset and should slave more to the other's velocity in the static-friction case.
position -= collisionInfo._penetration;
if (glm::length(relativeVelocity) < HALTING_ENTITY_SPEED) {
// static friction kicks in and entities moves with colliding object
velocity = collisionInfo._addedVelocity;
} else {
glm::vec3 direction = glm::normalize(collisionInfo._penetration);
velocity += glm::dot(relativeVelocity, direction) * (1.0f + collisionInfo._elasticity) * direction; // dynamic reflection
velocity += glm::clamp(collisionInfo._damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction
}
}
EntityItemProperties properties = entity->getProperties();
EntityItemID entityItemID(entity->getID());
properties.setPosition(position * (float)TREE_SCALE);
properties.setVelocity(velocity * (float)TREE_SCALE);
properties.setLastEdited(usecTimestampNow());
_entityTree->updateEntity(entity, properties);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, entityItemID, properties);
}

View file

@ -1,65 +0,0 @@
//
// EntityCollisionSystem.h
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 9/23/14.
// Copyright 2013-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_EntityCollisionSystem_h
#define hifi_EntityCollisionSystem_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <AvatarHashMap.h>
#include <CollisionInfo.h>
#include <OctreePacketData.h>
#include <SharedUtil.h>
#include "EntityItem.h"
#include "SimpleEntitySimulation.h"
class AbstractAudioInterface;
class AvatarData;
class EntityEditPacketSender;
class EntityTree;
class EntityCollisionSystem : public QObject, public SimpleEntitySimulation {
Q_OBJECT
public:
EntityCollisionSystem();
void init(EntityEditPacketSender* packetSender, EntityTree* entities, AvatarHashMap* _avatars = NULL);
~EntityCollisionSystem();
void updateCollisions();
void checkEntity(EntityItem* Entity);
void updateCollisionWithEntities(EntityItem* Entity);
void updateCollisionWithAvatars(EntityItem* Entity);
void queueEntityPropertiesUpdate(EntityItem* Entity);
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
private:
void applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo);
static bool updateOperation(OctreeElement* element, void* extraData);
void emitGlobalEntityCollisionWithEntity(EntityItem* entityA, EntityItem* entityB, const Collision& penetration);
EntityEditPacketSender* _packetSender;
AbstractAudioInterface* _audio;
AvatarHashMap* _avatars;
CollisionList _collisions;
};
#endif // hifi_EntityCollisionSystem_h

View file

@ -36,6 +36,11 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemI
int sizeOut = 0;
if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) {
#ifdef WANT_DEBUG
qDebug() << "calling queueOctreeEditMessage()...";
qDebug() << " id:" << modelID;
qDebug() << " properties:" << properties;
#endif
queueOctreeEditMessage(type, bufferOut, sizeOut);
}
}

View file

@ -21,6 +21,8 @@
#include "EntityItem.h"
#include "EntityTree.h"
bool EntityItem::_sendPhysicsUpdates = true;
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_id = entityItemID.id;
_creatorTokenID = entityItemID.creatorTokenID;
@ -140,9 +142,17 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
ByteCountCoded<quint32> typeCoder = getType();
QByteArray encodedType = typeCoder;
quint64 updateDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited();
// last updated (animations, non-physics changes)
quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited();
ByteCountCoded<quint64> updateDeltaCoder = updateDelta;
QByteArray encodedUpdateDelta = updateDeltaCoder;
// last simulated (velocity, angular velocity, physics changes)
quint64 simulatedDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited();
ByteCountCoded<quint64> simulatedDeltaCoder = simulatedDelta;
QByteArray encodedSimulatedDelta = simulatedDeltaCoder;
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
EntityPropertyFlags requestedProperties = getEntityProperties(params);
EntityPropertyFlags propertiesDidntFit = requestedProperties;
@ -157,19 +167,19 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
quint64 lastEdited = getLastEdited();
const bool wantDebug = false;
if (wantDebug) {
#ifdef WANT_DEBUG
float editedAgo = getEditedAgo();
QString agoAsString = formatSecondsElapsed(editedAgo);
qDebug() << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited
<< " ago=" << editedAgo << "seconds - " << agoAsString;
}
#endif
bool successIDFits = false;
bool successTypeFits = false;
bool successCreatedFits = false;
bool successLastEditedFits = false;
bool successLastUpdatedFits = false;
bool successLastSimulatedFits = false;
bool successPropertyFlagsFits = false;
int propertyFlagsOffset = 0;
int oldPropertyFlagsLength = 0;
@ -189,8 +199,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
if (successLastEditedFits) {
successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta);
}
if (successLastUpdatedFits) {
successLastSimulatedFits = packetData->appendValue(encodedSimulatedDelta);
}
if (successLastSimulatedFits) {
propertyFlagsOffset = packetData->getUncompressedByteOffset();
encodedPropertyFlags = propertyFlags;
oldPropertyFlagsLength = encodedPropertyFlags.length();
@ -213,11 +226,6 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, getPosition());
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, getDimensions()); // NOTE: PROP_RADIUS obsolete
if (wantDebug) {
qDebug() << " APPEND_ENTITY_PROPERTY() PROP_DIMENSIONS:" << getDimensions();
}
APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation());
APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, getDensity());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity());
@ -296,7 +304,6 @@ int EntityItem::expectedBytes() {
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
bool wantDebug = false;
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
@ -361,17 +368,21 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
_created = createdFromBuffer;
}
if (wantDebug) {
#ifdef WANT_DEBUG
quint64 lastEdited = getLastEdited();
float editedAgo = getEditedAgo();
QString agoAsString = formatSecondsElapsed(editedAgo);
QString ageAsString = formatSecondsElapsed(getAge());
qDebug() << "------------------------------------------";
qDebug() << "Loading entity " << getEntityItemID() << " from buffer...";
qDebug() << "------------------------------------------";
debugDump();
qDebug() << "------------------------------------------";
qDebug() << " _created =" << _created;
qDebug() << " age=" << getAge() << "seconds - " << ageAsString;
qDebug() << " lastEdited =" << lastEdited;
qDebug() << " ago=" << editedAgo << "seconds - " << agoAsString;
}
#endif
quint64 lastEditedFromBuffer = 0;
quint64 lastEditedFromBufferAdjusted = 0;
@ -388,20 +399,18 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug() << "data from server **************** ";
qDebug() << " entityItemID=" << getEntityItemID();
qDebug() << " now=" << now;
qDebug() << " getLastEdited()=" << getLastEdited();
qDebug() << " lastEditedFromBuffer=" << lastEditedFromBuffer << " (BEFORE clockskew adjust)";
qDebug() << " clockSkew=" << clockSkew;
qDebug() << " lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted << " (AFTER clockskew adjust)";
qDebug() << " _lastEditedFromRemote=" << _lastEditedFromRemote
<< " (our local time the last server edit we accepted)";
qDebug() << " _lastEditedFromRemoteInRemoteTime=" << _lastEditedFromRemoteInRemoteTime
<< " (remote time the last server edit we accepted)";
qDebug() << " fromSameServerEdit=" << fromSameServerEdit;
}
qDebug() << " entityItemID:" << getEntityItemID();
qDebug() << " now:" << now;
qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now);
qDebug() << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now);
qDebug() << " clockSkew:" << debugTimeOnly(clockSkew);
qDebug() << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now);
qDebug() << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now);
qDebug() << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now);
qDebug() << " fromSameServerEdit:" << fromSameServerEdit;
#endif
bool ignoreServerPacket = false; // assume we'll use this server packet
@ -424,14 +433,16 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
if (ignoreServerPacket) {
overwriteLocalData = false;
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug() << "IGNORING old data from server!!! ****************";
}
debugDump();
#endif
} else {
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug() << "USING NEW data from server!!! ****************";
}
debugDump();
#endif
// don't allow _lastEdited to be in the future
_lastEdited = lastEditedFromBufferAdjusted;
@ -449,15 +460,44 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
quint64 updateDelta = updateDeltaCoder;
if (overwriteLocalData) {
_lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that
if (wantDebug) {
qDebug() << "_lastUpdated =" << _lastUpdated;
qDebug() << "_lastEdited=" << _lastEdited;
qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted;
}
#ifdef WANT_DEBUG
qDebug() << " _lastUpdated:" << debugTime(_lastUpdated, now);
qDebug() << " _lastEdited:" << debugTime(_lastEdited, now);
qDebug() << " 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) {
_lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that
#ifdef WANT_DEBUG
qDebug() << " _lastSimulated:" << debugTime(_lastSimulated, now);
qDebug() << " _lastEdited:" << debugTime(_lastEdited, now);
qDebug() << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now);
#endif
}
encodedSimulatedDelta = simulatedDeltaCoder; // determine true length
dataAt += encodedSimulatedDelta.size();
bytesRead += encodedSimulatedDelta.size();
}
#ifdef WANT_DEBUG
if (overwriteLocalData) {
qDebug() << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID();
qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now);
qDebug() << " getLastSimulated:" << debugTime(getLastSimulated(), now);
qDebug() << " getLastUpdated:" << debugTime(getLastUpdated(), now);
}
#endif
// Property Flags
QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size
@ -477,23 +517,11 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
if (overwriteLocalData) {
setRadius(fromBuffer);
}
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() OLD FORMAT... found PROP_RADIUS";
}
}
} else {
READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions);
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() NEW FORMAT... look for PROP_DIMENSIONS";
}
}
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() _dimensions:" << getDimensionsInMeters() << " in meters";
}
READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation);
READ_ENTITY_PROPERTY_SETTER(PROP_DENSITY, float, updateDensity);
READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity);
@ -510,17 +538,22 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked);
READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA,setUserData);
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint;
qDebug() << " readEntityDataFromBuffer() _visible:" << _visible;
qDebug() << " readEntityDataFromBuffer() _ignoreForCollisions:" << _ignoreForCollisions;
qDebug() << " readEntityDataFromBuffer() _collisionsWillMove:" << _collisionsWillMove;
}
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
recalculateCollisionShape();
if (overwriteLocalData && (getDirtyFlags() & EntityItem::DIRTY_POSITION)) {
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) {
// 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.
float skipTimeForward = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND);
if (skipTimeForward > 0.0f) {
#ifdef WANT_DEBUG
qDebug() << "skipTimeForward:" << skipTimeForward;
#endif
simulateKinematicMotion(skipTimeForward);
}
_lastSimulated = now;
}
}
@ -546,13 +579,12 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s
memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime));
quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew;
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
const bool wantDebug = false;
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug("EntityItem::adjustEditPacketForClockSkew()...");
qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime;
qDebug() << " clockSkew: " << clockSkew;
qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime;
}
#endif
}
float EntityItem::computeMass() const {
@ -607,20 +639,13 @@ bool EntityItem::isRestingOnSurface() const {
}
void EntityItem::simulate(const quint64& now) {
if (_physicsInfo) {
// we rely on bullet for simulation, so bail
return;
}
bool wantDebug = false;
if (_lastSimulated == 0) {
_lastSimulated = now;
}
float timeElapsed = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND);
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug() << "********** EntityItem::simulate()";
qDebug() << " entity ID=" << getEntityItemID();
qDebug() << " now=" << now;
@ -655,23 +680,23 @@ void EntityItem::simulate(const quint64& now) {
qDebug() << " getAge()=" << getAge();
qDebug() << " getLifetime()=" << getLifetime();
}
}
if (wantDebug) {
qDebug() << " ********** EntityItem::simulate() .... SETTING _lastSimulated=" << _lastSimulated;
}
#endif
simulateKinematicMotion(timeElapsed);
_lastSimulated = now;
}
void EntityItem::simulateKinematicMotion(float timeElapsed) {
if (hasAngularVelocity()) {
glm::quat rotation = getRotation();
// angular damping
glm::vec3 angularVelocity = getAngularVelocity();
if (_angularDamping > 0.0f) {
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug() << " angularDamping :" << _angularDamping;
qDebug() << " newAngularVelocity:" << angularVelocity;
}
#endif
setAngularVelocity(angularVelocity);
}
@ -679,6 +704,9 @@ void EntityItem::simulate(const quint64& now) {
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
if (angularSpeed > 0.0f) {
_dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
}
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
} else {
// NOTE: angularSpeed is currently in degrees/sec!!!
@ -686,7 +714,7 @@ void EntityItem::simulate(const quint64& now) {
float angle = timeElapsed * glm::radians(angularSpeed);
glm::vec3 axis = _angularVelocity / angularSpeed;
glm::quat dQ = glm::angleAxis(angle, axis);
rotation = glm::normalize(dQ * rotation);
glm::quat rotation = glm::normalize(dQ * getRotation());
setRotation(rotation);
}
}
@ -696,19 +724,19 @@ void EntityItem::simulate(const quint64& now) {
glm::vec3 velocity = getVelocity();
if (_damping > 0.0f) {
velocity *= powf(1.0f - _damping, timeElapsed);
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug() << " damping:" << _damping;
qDebug() << " velocity AFTER dampingResistance:" << velocity;
qDebug() << " glm::length(velocity):" << glm::length(velocity);
qDebug() << " velocityEspilon :" << ENTITY_ITEM_EPSILON_VELOCITY_LENGTH;
}
#endif
}
// integrate position forward
glm::vec3 position = getPosition();
glm::vec3 newPosition = position + (velocity * timeElapsed);
if (wantDebug) {
#ifdef WANT_DEBUG
qDebug() << " EntityItem::simulate()....";
qDebug() << " timeElapsed:" << timeElapsed;
qDebug() << " old AACube:" << getMaximumAACube();
@ -718,91 +746,35 @@ void EntityItem::simulate(const quint64& now) {
qDebug() << " getDistanceToBottomOfEntity():" << getDistanceToBottomOfEntity() * (float)TREE_SCALE << " in meters";
qDebug() << " newPosition:" << newPosition;
qDebug() << " glm::distance(newPosition, position):" << glm::distance(newPosition, position);
}
#endif
position = newPosition;
// handle bounces off the ground... We bounce at the distance to the bottom of our entity
if (position.y <= getDistanceToBottomOfEntity()) {
velocity = velocity * glm::vec3(1,-1,1);
position.y = getDistanceToBottomOfEntity();
}
// apply gravity
if (hasGravity()) {
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
// "ground" plane of the domain, but for now it's what we've got
if (isRestingOnSurface()) {
velocity.y = 0.0f;
position.y = getDistanceToBottomOfEntity();
} else {
velocity += getGravity() * timeElapsed;
}
}
// NOTE: we don't zero out very small velocities --> we rely on a remote Bullet simulation
// to tell us when the entity has stopped.
// NOTE: the simulation should NOT set any DirtyFlags on this entity
setPosition(position); // this will automatically recalculate our collision shape
setVelocity(velocity);
if (wantDebug) {
qDebug() << " new position:" << position;
qDebug() << " new velocity:" << velocity;
qDebug() << " new AACube:" << getMaximumAACube();
qDebug() << " old getAABox:" << getAABox();
}
}
_lastSimulated = now;
}
void EntityItem::simulateSimpleKinematicMotion(float timeElapsed) {
if (hasAngularVelocity()) {
// angular damping
glm::vec3 angularVelocity = getAngularVelocity();
if (_angularDamping > 0.0f) {
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
setAngularVelocity(angularVelocity);
}
float angularSpeed = glm::length(_angularVelocity);
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
} else {
// NOTE: angularSpeed is currently in degrees/sec!!!
// TODO: Andrew to convert to radians/sec
float angle = timeElapsed * glm::radians(angularSpeed);
glm::vec3 axis = _angularVelocity / angularSpeed;
glm::quat dQ = glm::angleAxis(angle, axis);
glm::quat rotation = getRotation();
rotation = glm::normalize(dQ * rotation);
setRotation(rotation);
}
}
if (hasVelocity()) {
// linear damping
glm::vec3 velocity = getVelocity();
if (_damping > 0.0f) {
velocity *= powf(1.0f - _damping, timeElapsed);
}
// integrate position forward
glm::vec3 position = getPosition() + (velocity * timeElapsed);
// apply gravity
if (hasGravity()) {
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
// "ground" plane of the domain, but for now it's what we've got
velocity += getGravity() * timeElapsed;
}
// NOTE: the simulation should NOT set any DirtyFlags on this entity
setPosition(position); // this will automatically recalculate our collision shape
setVelocity(velocity);
float speed = glm::length(velocity);
const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; // 1mm/sec
if (speed < EPSILON_LINEAR_VELOCITY_LENGTH) {
setVelocity(ENTITY_ITEM_ZERO_VEC3);
if (speed > 0.0f) {
_dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
}
} else {
setPosition(position);
setVelocity(velocity);
}
#ifdef WANT_DEBUG
qDebug() << " new position:" << position;
qDebug() << " new velocity:" << velocity;
qDebug() << " new AACube:" << getMaximumAACube();
qDebug() << " old getAABox:" << getAABox();
#endif
}
}
@ -876,17 +848,17 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
if (somethingChanged) {
somethingChangedNotification(); // notify derived classes that something has changed
bool wantDebug = false;
uint64_t now = usecTimestampNow();
if (wantDebug) {
#ifdef WANT_DEBUG
int elapsed = now - getLastEdited();
qDebug() << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
}
#endif
if (_created != UNKNOWN_CREATED_TIME) {
setLastEdited(now);
}
if (getDirtyFlags() & EntityItem::DIRTY_POSITION) {
if (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
// TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases.
_lastSimulated = now;
}
}
@ -1023,15 +995,6 @@ void EntityItem::setRadius(float value) {
float diameter = value * 2.0f;
float maxDimension = sqrt((diameter * diameter) / 3.0f);
_dimensions = glm::vec3(maxDimension, maxDimension, maxDimension);
bool wantDebug = false;
if (wantDebug) {
qDebug() << "EntityItem::setRadius()...";
qDebug() << " radius:" << value;
qDebug() << " diameter:" << diameter;
qDebug() << " maxDimension:" << maxDimension;
qDebug() << " _dimensions:" << _dimensions;
}
}
// TODO: get rid of all users of this function...

View file

@ -36,13 +36,16 @@ class EntityTreeElementExtraEncodeData;
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { };
#define debugTime(T, N) qPrintable(QString("%1 [ %2 ago]").arg(T, 16, 10).arg(formatUsecTime(N - T), 15))
#define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10))
#define debugTreeVector(V) V << "[" << (V * (float)TREE_SCALE) << " in meters ]"
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
/// one directly, instead you must only construct one of it's derived classes with additional features.
class EntityItem {
friend class EntityTreeElement;
public:
enum EntityDirtyFlags {
DIRTY_POSITION = 0x0001,
@ -82,6 +85,7 @@ public:
void recordCreationTime(); // set _created to 'now'
quint64 getLastSimulated() const { return _lastSimulated; } /// Last simulated time of this entity universal usecs
void setLastSimulated(quint64 now) { _lastSimulated = now; }
/// Last edited time of this entity universal usecs
quint64 getLastEdited() const { return _lastEdited; }
@ -125,12 +129,12 @@ public:
// perform update
virtual void update(const quint64& now) { _lastUpdated = now; }
quint64 getLastUpdated() const { return _lastUpdated; }
// perform linear extrapolation for SimpleEntitySimulation
void simulate(const quint64& now);
void simulateKinematicMotion(float timeElapsed);
void simulateSimpleKinematicMotion(float timeElapsed);
virtual bool needsToCallUpdate() const { return false; }
virtual void debugDump() const;
@ -287,8 +291,15 @@ public:
void setPhysicsInfo(void* data) { _physicsInfo = data; }
EntityTreeElement* getElement() const { return _element; }
static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; }
static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; }
protected:
static bool _sendPhysicsUpdates;
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
virtual void recalculateCollisionShape();
@ -296,9 +307,10 @@ protected:
QUuid _id;
uint32_t _creatorTokenID;
bool _newlyCreated;
quint64 _lastSimulated; // last time this entity called simulate()
quint64 _lastUpdated; // last time this entity called update()
quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, and physics changes
quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes
quint64 _lastEdited; // last official local or remote edit time
quint64 _lastEditedFromRemote; // last time we received and edit from the server
quint64 _lastEditedFromRemoteInRemoteTime; // last time we received and edit from the server (in server-time-frame)
quint64 _created;

View file

@ -14,6 +14,7 @@
#include "LightEntityItem.h"
#include "ModelEntityItem.h"
EntityScriptingInterface::EntityScriptingInterface() :
_nextCreatorTokenID(0),
_entityTree(NULL)
@ -144,15 +145,26 @@ void EntityScriptingInterface::deleteEntity(EntityItemID entityID) {
}
}
bool shouldDelete = true;
// If we have a local entity tree set, then also update it.
if (_entityTree) {
_entityTree->lockForWrite();
_entityTree->deleteEntity(entityID);
EntityItem* entity = const_cast<EntityItem*>(_entityTree->findEntityByEntityItemID(actualID));
if (entity) {
if (entity->getLocked()) {
shouldDelete = false;
} else {
_entityTree->deleteEntity(entityID);
}
}
_entityTree->unlock();
}
// if at this point, we know the id, send the update to the entity server
if (entityID.isKnownID) {
// if at this point, we know the id, and we should still delete the entity, send the update to the entity server
if (shouldDelete && entityID.isKnownID) {
getEntityPacketSender()->queueEraseEntityMessage(entityID);
}
}
@ -234,6 +246,14 @@ bool EntityScriptingInterface::getLightsArePickable() const {
return LightEntityItem::getLightsArePickable();
}
void EntityScriptingInterface::setSendPhysicsUpdates(bool value) {
EntityItem::setSendPhysicsUpdates(value);
}
bool EntityScriptingInterface::getSendPhysicsUpdates() const {
return EntityItem::getSendPhysicsUpdates();
}
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
intersects(false),

View file

@ -99,6 +99,9 @@ public slots:
Q_INVOKABLE void setLightsArePickable(bool value);
Q_INVOKABLE bool getLightsArePickable() const;
Q_INVOKABLE void setSendPhysicsUpdates(bool value);
Q_INVOKABLE bool getSendPhysicsUpdates() const;
Q_INVOKABLE void dumpTree() const;
signals:

View file

@ -12,6 +12,7 @@
#ifndef hifi_EntitySimulation_h
#define hifi_EntitySimulation_h
#include <QtCore/QObject>
#include <QSet>
#include <PerfStat.h>
@ -31,7 +32,8 @@ const int DIRTY_SIMULATION_FLAGS =
EntityItem::DIRTY_LIFETIME |
EntityItem::DIRTY_UPDATEABLE;
class EntitySimulation {
class EntitySimulation : public QObject {
Q_OBJECT
public:
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL) { }
virtual ~EntitySimulation() { setEntityTree(NULL); }
@ -61,6 +63,9 @@ public:
EntityTree* getEntityTree() { return _entityTree; }
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
protected:
// These pure virtual methods are protected because they are not to be called will-nilly. The base class

View file

@ -229,6 +229,23 @@ void EntityTree::setSimulation(EntitySimulation* simulation) {
}
void EntityTree::deleteEntity(const EntityItemID& entityID) {
EntityTreeElement* containingElement = getContainingElement(entityID);
if (!containingElement) {
qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntity() entityID doesn't exist!!! entityID=" << entityID;
return;
}
EntityItem* existingEntity = containingElement->getEntityWithEntityItemID(entityID);
if (!existingEntity) {
qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntity() on entity items that don't exist. entityID=" << entityID;
return;
}
if (existingEntity->getLocked()) {
qDebug() << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID;
return;
}
emit deletingEntity(entityID);
// NOTE: callers must lock the tree before using this method
@ -242,14 +259,33 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs) {
// NOTE: callers must lock the tree before using this method
DeleteEntityOperator theOperator(this);
foreach(const EntityItemID& entityID, entityIDs) {
EntityTreeElement* containingElement = getContainingElement(entityID);
if (!containingElement) {
qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntities() entityID doesn't exist!!! entityID=" << entityID;
continue;
}
EntityItem* existingEntity = containingElement->getEntityWithEntityItemID(entityID);
if (!existingEntity) {
qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntities() on entity items that don't exist. entityID=" << entityID;
continue;
}
if (existingEntity->getLocked()) {
qDebug() << "ERROR! EntityTree::deleteEntities() trying to delete locked entity. entityID=" << entityID;
continue;
}
// tell our delete operator about this entityID
theOperator.addEntityIDToDeleteList(entityID);
emit deletingEntity(entityID);
}
recurseTreeWithOperator(&theOperator);
processRemovedEntities(theOperator);
_isDirty = true;
if (theOperator.getEntities().size() > 0) {
recurseTreeWithOperator(&theOperator);
processRemovedEntities(theOperator);
_isDirty = true;
}
}
void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) {

View file

@ -60,7 +60,8 @@ public:
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return true; }
virtual PacketType expectedDataPacketType() const { return PacketTypeEntityData; }
virtual bool canProcessVersion(PacketVersion thisVersion) const { return true; } // we support all versions
virtual bool canProcessVersion(PacketVersion thisVersion) const
{ return thisVersion >= VERSION_ENTITIES_SUPPORT_SPLIT_MTU; } // we support all versions with split mtu
virtual bool handlesEditPacketType(PacketType packetType) const;
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);

View file

@ -81,17 +81,6 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
return somethingChanged;
}
int ModelEntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
return oldVersionReadEntityDataFromBuffer(data, bytesLeftToRead, args);
}
// let our base class do most of the work... it will call us back for our porition...
return EntityItem::readEntityDataFromBuffer(data, bytesLeftToRead, args);
}
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
@ -131,122 +120,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
return bytesRead;
}
int ModelEntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
int bytesRead = 0;
if (bytesLeftToRead >= expectedBytes()) {
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
const unsigned char* dataAt = data;
// id
// this old bitstream format had 32bit IDs. They are obsolete and need to be replaced with our new UUID
// format. We can simply read and ignore the old ID since they should not be repeated. This code should only
// run on loading from an old file.
quint32 oldID;
memcpy(&oldID, dataAt, sizeof(oldID));
dataAt += sizeof(oldID);
bytesRead += sizeof(oldID);
_id = QUuid::createUuid();
// _lastUpdated
memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated));
dataAt += sizeof(_lastUpdated);
bytesRead += sizeof(_lastUpdated);
_lastUpdated -= clockSkew;
// _lastEdited
memcpy(&_lastEdited, dataAt, sizeof(_lastEdited));
dataAt += sizeof(_lastEdited);
bytesRead += sizeof(_lastEdited);
_lastEdited -= clockSkew;
_created = _lastEdited; // NOTE: old models didn't have age or created time, assume their last edit was a create
QString ageAsString = formatSecondsElapsed(getAge());
qDebug() << "Loading old model file, _created = _lastEdited =" << _created
<< " age=" << getAge() << "seconds - " << ageAsString
<< "old ID=" << oldID << "new ID=" << _id;
// radius
float radius;
memcpy(&radius, dataAt, sizeof(radius));
dataAt += sizeof(radius);
bytesRead += sizeof(radius);
setRadius(radius);
// position
memcpy(&_position, dataAt, sizeof(_position));
dataAt += sizeof(_position);
bytesRead += sizeof(_position);
// color
memcpy(&_color, dataAt, sizeof(_color));
dataAt += sizeof(_color);
bytesRead += sizeof(_color);
// TODO: how to handle this? Presumable, this would only ever be true if the model file was saved with
// a model being in a shouldBeDeleted state. Which seems unlikely. But if it happens, maybe we should delete the entity after loading?
// shouldBeDeleted
bool shouldBeDeleted = false;
memcpy(&shouldBeDeleted, dataAt, sizeof(shouldBeDeleted));
dataAt += sizeof(shouldBeDeleted);
bytesRead += sizeof(shouldBeDeleted);
if (shouldBeDeleted) {
qDebug() << "UNEXPECTED - read shouldBeDeleted=TRUE from an old format file";
}
// modelURL
uint16_t modelURLLength;
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
dataAt += sizeof(modelURLLength);
bytesRead += sizeof(modelURLLength);
QString modelURLString((const char*)dataAt);
setModelURL(modelURLString);
dataAt += modelURLLength;
bytesRead += modelURLLength;
// rotation
int bytes = unpackOrientationQuatFromBytes(dataAt, _rotation);
dataAt += bytes;
bytesRead += bytes;
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ANIMATION) {
// animationURL
uint16_t animationURLLength;
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
dataAt += sizeof(animationURLLength);
bytesRead += sizeof(animationURLLength);
QString animationURLString((const char*)dataAt);
setAnimationURL(animationURLString);
dataAt += animationURLLength;
bytesRead += animationURLLength;
// animationIsPlaying
bool animationIsPlaying;
memcpy(&animationIsPlaying, dataAt, sizeof(animationIsPlaying));
dataAt += sizeof(animationIsPlaying);
bytesRead += sizeof(animationIsPlaying);
setAnimationIsPlaying(animationIsPlaying);
// animationFrameIndex
float animationFrameIndex;
memcpy(&animationFrameIndex, dataAt, sizeof(animationFrameIndex));
dataAt += sizeof(animationFrameIndex);
bytesRead += sizeof(animationFrameIndex);
setAnimationFrameIndex(animationFrameIndex);
// animationFPS
float animationFPS;
memcpy(&animationFPS, dataAt, sizeof(animationFPS));
dataAt += sizeof(animationFPS);
bytesRead += sizeof(animationFPS);
setAnimationFPS(animationFPS);
}
}
return bytesRead;
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);

View file

@ -40,7 +40,6 @@ public:
OctreeElement::AppendState& appendState) const;
virtual int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
@ -116,8 +115,6 @@ public:
protected:
/// For reading models from pre V3 bitstreams
int oldVersionReadEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
bool isAnimatingSomething() const;
rgbColor _color;

View file

@ -132,3 +132,14 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons
}
return false;
}
void SphereEntityItem::debugDump() const {
quint64 now = usecTimestampNow();
qDebug() << "SHPERE EntityItem id:" << getEntityItemID() << "---------------------------------------------";
qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
qDebug() << " position:" << debugTreeVector(_position);
qDebug() << " dimensions:" << debugTreeVector(_dimensions);
qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now);
}

View file

@ -63,6 +63,8 @@ public:
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const;
virtual void debugDump() const;
protected:
virtual void recalculateCollisionShape();

View file

@ -149,12 +149,13 @@ void SetDataEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects
}
PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius,
float height, bool set, bool erase) :
float height, bool set, bool erase, float granularity) :
position(position),
radius(radius),
height(height),
set(set),
erase(erase) {
erase(erase),
granularity(granularity) {
}
void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
@ -166,7 +167,8 @@ void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObje
Box(position - extents, position + extents), results);
foreach (const SharedObjectPointer& spanner, results) {
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->paintHeight(position, radius, height, set, erase);
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->paintHeight(position, radius,
height, set, erase, granularity);
if (newSpanner != spanner) {
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
}
@ -179,11 +181,12 @@ MaterialEdit::MaterialEdit(const SharedObjectPointer& material, const QColor& av
}
HeightfieldMaterialSpannerEdit::HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner,
const SharedObjectPointer& material, const QColor& averageColor, bool paint, bool voxelize) :
const SharedObjectPointer& material, const QColor& averageColor, bool paint, bool voxelize, float granularity) :
MaterialEdit(material, averageColor),
spanner(spanner),
paint(paint),
voxelize(voxelize) {
voxelize(voxelize),
granularity(granularity) {
}
class SpannerProjectionFetchVisitor : public SpannerVisitor {
@ -250,16 +253,18 @@ void HeightfieldMaterialSpannerEdit::apply(MetavoxelData& data, const WeakShared
}
foreach (const SharedObjectPointer& result, results) {
Spanner* newResult = static_cast<Spanner*>(result.data())->setMaterial(spanner, material, color, paint, voxelize);
Spanner* newResult = static_cast<Spanner*>(result.data())->setMaterial(spanner, material,
color, paint, voxelize, granularity);
if (newResult != result) {
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), result, newResult);
}
}
}
FillHeightfieldHeightEdit::FillHeightfieldHeightEdit(const glm::vec3& position, float radius) :
FillHeightfieldHeightEdit::FillHeightfieldHeightEdit(const glm::vec3& position, float radius, float granularity) :
position(position),
radius(radius) {
radius(radius),
granularity(granularity) {
}
void FillHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
@ -269,7 +274,7 @@ void FillHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjec
Box(position - extents, position + extents), results);
foreach (const SharedObjectPointer& spanner, results) {
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->fillHeight(position, radius);
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->fillHeight(position, radius, granularity);
if (newSpanner != spanner) {
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
}

View file

@ -205,9 +205,10 @@ public:
STREAM float height;
STREAM bool set;
STREAM bool erase;
STREAM float granularity;
PaintHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f,
float height = 0.0f, bool set = false, bool erase = false);
float height = 0.0f, bool set = false, bool erase = false, float granularity = 0.0f);
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};
@ -237,10 +238,11 @@ public:
STREAM SharedObjectPointer spanner;
STREAM bool paint;
STREAM bool voxelize;
STREAM float granularity;
HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(),
const SharedObjectPointer& material = SharedObjectPointer(),
const QColor& averageColor = QColor(), bool paint = false, bool voxelize = false);
const QColor& averageColor = QColor(), bool paint = false, bool voxelize = false, float granularity = 0.0f);
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};
@ -255,8 +257,9 @@ public:
STREAM glm::vec3 position;
STREAM float radius;
STREAM float granularity;
FillHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f);
FillHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, float granularity = 0.0f);
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};

View file

@ -111,16 +111,16 @@ bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& dire
return _bounds.findRayIntersection(origin, direction, distance);
}
Spanner* Spanner::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) {
Spanner* Spanner::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase, float granularity) {
return this;
}
Spanner* Spanner::fillHeight(const glm::vec3& position, float radius) {
Spanner* Spanner::fillHeight(const glm::vec3& position, float radius, float granularity) {
return this;
}
Spanner* Spanner::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
const QColor& color, bool paint, bool voxelize) {
const QColor& color, bool paint, bool voxelize, float granularity) {
return this;
}
@ -1786,7 +1786,7 @@ void HeightfieldNode::getRangeAfterHeightPaint(const glm::vec3& translation, con
HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
const glm::vec3& position, float radius, float height, bool set, bool erase,
float normalizeScale, float normalizeOffset) {
float normalizeScale, float normalizeOffset, float granularity) {
if (!_height) {
return this;
}
@ -1813,7 +1813,7 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons
HeightfieldNode* newChild = _children[i]->paintHeight(translation +
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation,
nextScale, position, radius, height, set, erase, normalizeScale, normalizeOffset);
nextScale, position, radius, height, set, erase, normalizeScale, normalizeOffset, granularity);
if (_children[i] != newChild) {
if (newNode == this) {
newNode = new HeightfieldNode(*this);
@ -1846,6 +1846,13 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons
new HeightfieldStack(stackWidth, newStackContents, newStackMaterials)));
}
// if the granularity is insufficient, we must subdivide
if (scale.x / innerHeightWidth > granularity || scale.z / innerHeightHeight > granularity) {
HeightfieldNodePointer newNode(subdivide(newHeightContents, newStackContents));
return newNode->paintHeight(translation, rotation, scale, position, radius, height, set,
erase, 1.0f, 0.0f, granularity);
}
// now apply the actual change
glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(),
glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ));
@ -1885,7 +1892,7 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons
}
HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
const glm::vec3& position, float radius) {
const glm::vec3& position, float radius, float granularity) {
if (!_height) {
return this;
}
@ -1911,7 +1918,7 @@ HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const
HeightfieldNode* newChild = _children[i]->fillHeight(translation +
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation,
nextScale, position, radius);
nextScale, position, radius, granularity);
if (_children[i] != newChild) {
if (newNode == this) {
newNode = new HeightfieldNode(*this);
@ -1927,9 +1934,15 @@ HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const
if (!_stack) {
return this;
}
QVector<quint16> newHeightContents = _height->getContents();
// if the granularity is insufficient, we must subdivide
QVector<quint16> newHeightContents = _height->getContents();
QVector<StackArray> newStackContents = _stack->getContents();
if (scale.x / innerHeightWidth > granularity || scale.z / innerHeightHeight > granularity) {
HeightfieldNodePointer newNode(subdivide(newHeightContents, newStackContents));
return newNode->fillHeight(translation, rotation, scale, position, radius, granularity);
}
int stackWidth = _stack->getWidth();
int stackHeight = newStackContents.size() / stackWidth;
QVector<SharedObjectPointer> newStackMaterials = _stack->getMaterials();
@ -2088,7 +2101,7 @@ void HeightfieldNode::getRangeAfterEdit(const glm::vec3& translation, const glm:
HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize,
float normalizeScale, float normalizeOffset) {
float normalizeScale, float normalizeOffset, float granularity) {
if (!_height) {
return this;
}
@ -2109,7 +2122,7 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons
HeightfieldNode* newChild = _children[i]->setMaterial(translation +
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, spanner,
material, color, paint, voxelize, normalizeScale, normalizeOffset);
material, color, paint, voxelize, normalizeScale, normalizeOffset, granularity);
if (_children[i] != newChild) {
if (newNode == this) {
newNode = new HeightfieldNode(*this);
@ -2149,6 +2162,14 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons
return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)),
_color, _material, HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials)));
}
// if the granularity is insufficient, we must subdivide
if (scale.x / innerHeightWidth > granularity || scale.z / innerHeightHeight > granularity) {
HeightfieldNodePointer newNode(subdivide(newHeightContents, newStackContents));
return newNode->setMaterial(translation, rotation, scale, spanner, material, color,
paint, voxelize, 1.0f, 0.0f, granularity);
}
QVector<quint16> oldHeightContents = newHeightContents;
QVector<StackArray> oldStackContents = newStackContents;
@ -3210,6 +3231,210 @@ bool HeightfieldNode::findHeightfieldRayIntersection(const glm::vec3& origin, co
return false;
}
static inline float mixHeights(float firstHeight, float secondHeight, float t) {
return (firstHeight == 0.0f) ? secondHeight : (secondHeight == 0.0f ? firstHeight :
glm::mix(firstHeight, secondHeight, t));
}
HeightfieldNode* HeightfieldNode::subdivide(const QVector<quint16>& heightContents,
const QVector<StackArray>& stackContents) const {
HeightfieldNode* newNode = new HeightfieldNode(*this);
int heightWidth = _height->getWidth();
int heightHeight = heightContents.size() / heightWidth;
newNode->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, heightContents)));
int stackWidth = 0, stackHeight = 0;
QVector<SharedObjectPointer> stackMaterials;
if (_stack) {
stackWidth = _stack->getWidth();
stackHeight = stackContents.size() / stackWidth;
stackMaterials = _stack->getMaterials();
newNode->setStack(HeightfieldStackPointer(new HeightfieldStack(stackWidth, stackContents, stackMaterials)));
}
int colorWidth = 0, colorHeight = 0;
if (_color) {
colorWidth = _color->getWidth();
colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES);
}
int materialWidth = 0, materialHeight = 0;
QVector<SharedObjectPointer> materialMaterials;
if (_material) {
materialWidth = _material->getWidth();
materialHeight = _material->getContents().size() / materialWidth;
materialMaterials = _material->getMaterials();
}
for (int i = 0; i < CHILD_COUNT; i++) {
QVector<quint16> childHeightContents(heightWidth * heightHeight);
QByteArray childColorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF);
QByteArray childMaterialContents(materialWidth * materialHeight, 0);
QVector<StackArray> childStackContents(stackWidth * stackHeight);
quint16* heightDest = childHeightContents.data();
const quint16* heightSrc = heightContents.constData() + (i & Y_MAXIMUM_FLAG ? (heightHeight / 2) * heightWidth : 0) +
(i & X_MAXIMUM_FLAG ? heightWidth / 2 : 0);
for (int z = 0; z < heightHeight; z++) {
float srcZ = z * 0.5f + 0.5f;
float fractZ = glm::fract(srcZ);
const quint16* heightSrcZ = heightSrc + (int)srcZ * heightWidth;
for (int x = 0; x < heightWidth; x++) {
float srcX = x * 0.5f + 0.5f;
float fractX = glm::fract(srcX);
const quint16* heightSrcX = heightSrcZ + (int)srcX;
if (fractZ == 0.0f) {
if (fractX == 0.0f) {
*heightDest++ = heightSrcX[0];
} else {
*heightDest++ = mixHeights(heightSrcX[0], heightSrcX[1], fractX);
}
} else {
if (fractX == 0.0f) {
*heightDest++ = mixHeights(heightSrcX[0], heightSrcX[heightWidth], fractZ);
} else {
*heightDest++ = mixHeights(mixHeights(heightSrcX[0], heightSrcX[1], fractX),
mixHeights(heightSrcX[heightWidth], heightSrcX[heightWidth + 1], fractX), fractZ);
}
}
}
}
if (colorWidth != 0) {
char* colorDest = childColorContents.data();
const uchar* colorSrc = (const uchar*)_color->getContents().constData() +
((i & Y_MAXIMUM_FLAG ? (colorHeight / 2) * colorWidth : 0) +
(i & X_MAXIMUM_FLAG ? colorWidth / 2 : 0)) * DataBlock::COLOR_BYTES;
for (int z = 0; z < colorHeight; z++) {
float srcZ = z * 0.5f;
float fractZ = glm::fract(srcZ);
const uchar* colorSrcZ = colorSrc + (int)srcZ * colorWidth * DataBlock::COLOR_BYTES;
for (int x = 0; x < colorWidth; x++) {
float srcX = x * 0.5f;
float fractX = glm::fract(srcX);
const uchar* colorSrcX = colorSrcZ + (int)srcX * DataBlock::COLOR_BYTES;
const uchar* nextColorSrcX = colorSrcX + colorWidth * DataBlock::COLOR_BYTES;
if (fractZ == 0.0f) {
if (fractX == 0.0f) {
*colorDest++ = colorSrcX[0];
*colorDest++ = colorSrcX[1];
*colorDest++ = colorSrcX[2];
} else {
*colorDest++ = glm::mix(colorSrcX[0], colorSrcX[3], fractX);
*colorDest++ = glm::mix(colorSrcX[1], colorSrcX[4], fractX);
*colorDest++ = glm::mix(colorSrcX[2], colorSrcX[5], fractX);
}
} else {
if (fractX == 0.0f) {
*colorDest++ = glm::mix(colorSrcX[0], nextColorSrcX[0], fractZ);
*colorDest++ = glm::mix(colorSrcX[1], nextColorSrcX[1], fractZ);
*colorDest++ = glm::mix(colorSrcX[2], nextColorSrcX[2], fractZ);
} else {
*colorDest++ = glm::mix(glm::mix(colorSrcX[0], colorSrcX[3], fractX),
glm::mix(nextColorSrcX[0], nextColorSrcX[3], fractX), fractZ);
*colorDest++ = glm::mix(glm::mix(colorSrcX[1], colorSrcX[4], fractX),
glm::mix(nextColorSrcX[1], nextColorSrcX[4], fractX), fractZ);
*colorDest++ = glm::mix(glm::mix(colorSrcX[2], colorSrcX[5], fractX),
glm::mix(nextColorSrcX[2], nextColorSrcX[5], fractX), fractZ);
}
}
}
}
}
if (materialWidth != 0) {
char* materialDest = childMaterialContents.data();
const char* materialSrc = _material->getContents().constData() +
(i & Y_MAXIMUM_FLAG ? (materialHeight / 2) * materialWidth : 0) +
(i & X_MAXIMUM_FLAG ? materialWidth / 2 : 0);
for (int z = 0; z < materialHeight; z++) {
float srcZ = z * 0.5f;
const char* materialSrcZ = materialSrc + (int)srcZ * materialWidth;
for (int x = 0; x < materialWidth; x++) {
float srcX = x * 0.5f;
const char* materialSrcX = materialSrcZ + (int)srcX;
*materialDest++ = *materialSrcX;
}
}
}
if (stackWidth != 0) {
StackArray* stackDest = childStackContents.data();
const StackArray* stackSrc = _stack->getContents().constData() +
(i & Y_MAXIMUM_FLAG ? (stackHeight / 2) * stackWidth : 0) +
(i & X_MAXIMUM_FLAG ? stackWidth / 2 : 0);
for (int z = 0; z < stackHeight; z++) {
float srcZ = z * 0.5f;
float fractZ = glm::fract(srcZ);
const StackArray* stackSrcZ = stackSrc + (int)srcZ * stackWidth;
for (int x = 0; x < stackWidth; x++) {
float srcX = x * 0.5f;
float fractX = glm::fract(srcX);
const StackArray* stackSrcX = stackSrcZ + (int)srcX;
if (stackSrcX->isEmpty()) {
stackDest++;
continue;
}
int minimumY = stackSrcX->getPosition() * 2;
int maximumY = (stackSrcX->getPosition() + stackSrcX->getEntryCount() - 1) * 2;
*stackDest = StackArray(maximumY - minimumY + 1);
stackDest->setPosition(minimumY);
for (int y = minimumY; y <= maximumY; y++) {
float srcY = y * 0.5f;
float fractY = glm::fract(srcY);
const StackArray::Entry& srcEntry = stackSrcX->getEntry((int)srcY);
StackArray::Entry& destEntry = stackDest->getEntry(y);
destEntry.color = srcEntry.color;
destEntry.material = srcEntry.material;
if (srcEntry.hermiteX != 0) {
glm::vec3 normal;
float distance = srcEntry.getHermiteX(normal);
if (distance < fractX) {
const StackArray::Entry& nextSrcEntryX = stackSrcX[1].getEntry((int)srcY);
destEntry.color = nextSrcEntryX.color;
destEntry.material = nextSrcEntryX.material;
} else {
destEntry.setHermiteX(normal, (distance - fractX) / 0.5f);
}
}
if (srcEntry.hermiteY != 0) {
glm::vec3 normal;
float distance = srcEntry.getHermiteY(normal);
if (distance < fractY) {
const StackArray::Entry& nextSrcEntryY = stackSrcX->getEntry((int)srcY + 1);
destEntry.color = nextSrcEntryY.color;
destEntry.material = nextSrcEntryY.material;
} else {
destEntry.setHermiteY(normal, (distance - fractY) / 0.5f);
}
}
if (srcEntry.hermiteZ != 0) {
glm::vec3 normal;
float distance = srcEntry.getHermiteZ(normal);
if (distance < fractZ) {
const StackArray::Entry& nextSrcEntryZ = stackSrcX[stackWidth].getEntry((int)srcY);
destEntry.color = nextSrcEntryZ.color;
destEntry.material = nextSrcEntryZ.material;
} else {
destEntry.setHermiteZ(normal, (distance - fractZ) / 0.5f);
}
}
}
stackDest++;
}
}
}
newNode->setChild(i, HeightfieldNodePointer(new HeightfieldNode(
HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, childHeightContents)),
HeightfieldColorPointer(colorWidth == 0 ? NULL : new HeightfieldColor(colorWidth, childColorContents)),
HeightfieldMaterialPointer(materialWidth == 0 ? NULL :
new HeightfieldMaterial(materialWidth, childMaterialContents, materialMaterials)),
HeightfieldStackPointer(stackWidth == 0 ? NULL :
new HeightfieldStack(stackWidth, childStackContents, stackMaterials)))));
}
return newNode;
}
AbstractHeightfieldNodeRenderer::~AbstractHeightfieldNodeRenderer() {
}
@ -3310,7 +3535,8 @@ bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3&
getScale() * _aspectZ), origin, direction, distance);
}
Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) {
Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height,
bool set, bool erase, float granularity) {
// first see if we're going to exceed the range limits
float minimumValue = 1.0f, maximumValue = numeric_limits<quint16>::max();
if (set) {
@ -3328,19 +3554,19 @@ Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float
Heightfield* newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset);
newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(newHeightfield->getTranslation(), getRotation(),
glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), position, radius, height,
set, erase, normalizeScale, normalizeOffset)));
set, erase, normalizeScale, normalizeOffset, granularity)));
return newHeightfield;
}
Spanner* Heightfield::fillHeight(const glm::vec3& position, float radius) {
Spanner* Heightfield::fillHeight(const glm::vec3& position, float radius, float granularity) {
Heightfield* newHeightfield = static_cast<Heightfield*>(clone(true));
newHeightfield->setRoot(HeightfieldNodePointer(_root->fillHeight(getTranslation(), getRotation(),
glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), position, radius)));
glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), position, radius, granularity)));
return newHeightfield;
}
Spanner* Heightfield::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
const QColor& color, bool paint, bool voxelize) {
const QColor& color, bool paint, bool voxelize, float granularity) {
// first see if we're going to exceed the range limits, normalizing if necessary
Spanner* spannerData = static_cast<Spanner*>(spanner.data());
float normalizeScale = 1.0f, normalizeOffset = 0.0f;
@ -3355,7 +3581,7 @@ Spanner* Heightfield::setMaterial(const SharedObjectPointer& spanner, const Shar
}
newHeightfield->setRoot(HeightfieldNodePointer(_root->setMaterial(newHeightfield->getTranslation(), getRotation(),
glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), spannerData,
material, color, paint, voxelize, normalizeScale, normalizeOffset)));
material, color, paint, voxelize, normalizeScale, normalizeOffset, granularity)));
return newHeightfield;
}

View file

@ -77,16 +77,17 @@ public:
/// \param set whether to set the height as opposed to raising/lowering it
/// \param erase whether to erase height values
/// \return the modified spanner, or this if no modification was performed
virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase);
virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height,
bool set, bool erase, float granularity);
/// Attempts to fill the spanner's height (adding removing volumetric information).
/// \return the modified spanner, or this if no modification was performed
virtual Spanner* fillHeight(const glm::vec3& position, float radius);
virtual Spanner* fillHeight(const glm::vec3& position, float radius, float granularity);
/// Attempts to "sculpt" or "paint," etc., with the supplied spanner.
/// \return the modified spanner, or this if no modification was performed
virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
const QColor& color, bool paint, bool voxelize);
const QColor& color, bool paint, bool voxelize, float granularity);
/// Checks whether this spanner has its own colors.
virtual bool hasOwnColors() const;
@ -696,17 +697,17 @@ public:
HeightfieldNode* paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
const glm::vec3& position, float radius, float height, bool set, bool erase,
float normalizeScale, float normalizeOffset);
float normalizeScale, float normalizeOffset, float granularity);
HeightfieldNode* fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
const glm::vec3& position, float radius);
const glm::vec3& position, float radius, float granularity);
void getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
const Box& editBounds, float& minimum, float& maximum) const;
HeightfieldNode* setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize,
float normalizeScale, float normalizeOffset);
float normalizeScale, float normalizeOffset, float granularity);
void read(HeightfieldStreamState& state);
void write(HeightfieldStreamState& state) const;
@ -736,6 +737,8 @@ private:
bool findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float boundsDistance, float& distance) const;
HeightfieldNode* subdivide(const QVector<quint16>& heightContents, const QVector<StackArray>& stackContents) const;
HeightfieldHeightPointer _height;
HeightfieldColorPointer _color;
HeightfieldMaterialPointer _material;
@ -803,12 +806,13 @@ public:
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase);
virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height,
bool set, bool erase, float granularity);
virtual Spanner* fillHeight(const glm::vec3& position, float radius);
virtual Spanner* fillHeight(const glm::vec3& position, float radius, float granularity);
virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
const QColor& color, bool paint, bool voxelize);
const QColor& color, bool paint, bool voxelize, float granularity);
virtual bool hasOwnColors() const;
virtual bool hasOwnMaterials() const;

View file

@ -72,13 +72,13 @@ PacketVersion versionForPacketType(PacketType type) {
return 1;
case PacketTypeEntityAddOrEdit:
case PacketTypeEntityData:
return VERSION_ENTITIES_HAVE_USER_DATA;
return VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME;
case PacketTypeEntityErase:
return 2;
case PacketTypeAudioStreamStats:
return 1;
case PacketTypeMetavoxelData:
return 12;
return 13;
default:
return 0;
}

View file

@ -126,6 +126,7 @@ const PacketVersion VERSION_ENTITIES_HAS_FILE_BREAKS = VERSION_ENTITIES_SUPPORT_
const PacketVersion VERSION_ENTITIES_SUPPORT_DIMENSIONS = 4;
const PacketVersion VERSION_ENTITIES_MODELS_HAVE_ANIMATION_SETTINGS = 5;
const PacketVersion VERSION_ENTITIES_HAVE_USER_DATA = 6;
const PacketVersion VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME = 7;
const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1;
#endif // hifi_PacketHeaders_h

View file

@ -0,0 +1,29 @@
//
// ContactEvent.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.20
// 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 "BulletUtil.h"
#include "ContactInfo.h"
void ContactInfo::update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset) {
_lastStep = currentStep;
++_numSteps;
contactPoint = bulletToGLM(p.m_positionWorldOnB) + worldOffset;
penetration = bulletToGLM(p.m_distance1 * p.m_normalWorldOnB);
// TODO: also report normal
//_normal = bulletToGLM(p.m_normalWorldOnB);
}
ContactEventType ContactInfo::computeType(uint32_t thisStep) {
if (_lastStep != thisStep) {
return CONTACT_EVENT_TYPE_END;
}
return (_numSteps == 1) ? CONTACT_EVENT_TYPE_START : CONTACT_EVENT_TYPE_CONTINUE;
}

View file

@ -0,0 +1,37 @@
//
// ContactEvent.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.20
// 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_ContactEvent_h
#define hifi_ContactEvent_h
#include <btBulletDynamicsCommon.h>
#include <glm/glm.hpp>
#include "RegisteredMetaTypes.h"
enum ContactEventType {
CONTACT_EVENT_TYPE_START,
CONTACT_EVENT_TYPE_CONTINUE,
CONTACT_EVENT_TYPE_END
};
class ContactInfo : public Collision
{
public:
void update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset);
ContactEventType computeType(uint32_t thisStep);
private:
uint32_t _lastStep = 0;
uint32_t _numSteps = 0;
};
#endif // hifi_ContactEvent_h

View file

@ -14,7 +14,7 @@
#include "BulletUtil.h"
#include "EntityMotionState.h"
#include "SimpleEntityKinematicController.h"
#include "PhysicsEngine.h"
QSet<EntityItem*>* _outgoingEntityList;
@ -33,6 +33,7 @@ void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) {
EntityMotionState::EntityMotionState(EntityItem* entity)
: _entity(entity) {
_type = MOTION_STATE_TYPE_ENTITY;
assert(entity != NULL);
}
@ -40,8 +41,6 @@ EntityMotionState::~EntityMotionState() {
assert(_entity);
_entity->setPhysicsInfo(NULL);
_entity = NULL;
delete _kinematicController;
_kinematicController = NULL;
}
MotionType EntityMotionState::computeMotionType() const {
@ -51,13 +50,16 @@ MotionType EntityMotionState::computeMotionType() const {
return _entity->isMoving() ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC;
}
void EntityMotionState::addKinematicController() {
if (!_kinematicController) {
_kinematicController = new SimpleEntityKinematicController(_entity);
_kinematicController->start();
} else {
_kinematicController->start();
}
void EntityMotionState::updateKinematicState(uint32_t substep) {
setKinematic(_entity->isMoving(), substep);
}
void EntityMotionState::stepKinematicSimulation(quint64 now) {
assert(_isKinematic);
// NOTE: this is non-physical kinematic motion which steps to real run-time (now)
// which is different from physical kinematic motion (inside getWorldTransform())
// which steps in physics simulation time.
_entity->simulate(now);
}
// This callback is invoked by the physics simulation in two cases:
@ -66,8 +68,16 @@ void EntityMotionState::addKinematicController() {
// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's --
// it is an opportunity for outside code to update the object's simulation position
void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
if (_kinematicController && _kinematicController->isRunning()) {
_kinematicController->stepForward();
if (_isKinematic) {
// This is physical kinematic motion which steps strictly by the subframe count
// of the physics simulation.
uint32_t substep = PhysicsEngine::getNumSubsteps();
float dt = (substep - _lastKinematicSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_entity->simulateKinematicMotion(dt);
_entity->setLastSimulated(usecTimestampNow());
// bypass const-ness so we can remember the substep
const_cast<EntityMotionState*>(this)->_lastKinematicSubstep = substep;
}
worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset()));
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
@ -87,8 +97,18 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
// DANGER! EntityItem stores angularVelocity in degrees/sec!!!
_entity->setAngularVelocity(glm::degrees(v));
_entity->setLastSimulated(usecTimestampNow());
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
EntityMotionState::enqueueOutgoingEntity(_entity);
#ifdef WANT_DEBUG
quint64 now = usecTimestampNow();
qDebug() << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID();
qDebug() << " last edited:" << _entity->getLastEdited() << formatUsecTime(now - _entity->getLastEdited()) << "ago";
qDebug() << " last simulated:" << _entity->getLastSimulated() << formatUsecTime(now - _entity->getLastSimulated()) << "ago";
qDebug() << " last updated:" << _entity->getLastUpdated() << formatUsecTime(now - _entity->getLastUpdated()) << "ago";
#endif
}
void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) {
@ -207,16 +227,34 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
if (_numNonMovingUpdates <= 1) {
// we only update lastEdited when we're sending new physics data
// (i.e. NOT when we just simulate the positions forward, nore when we resend non-moving data)
// NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly
quint64 lastSimulated = _entity->getLastSimulated();
_entity->setLastEdited(lastSimulated);
properties.setLastEdited(lastSimulated);
#ifdef WANT_DEBUG
quint64 now = usecTimestampNow();
qDebug() << "EntityMotionState::sendUpdate()";
qDebug() << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------";
qDebug() << " lastSimulated:" << debugTime(lastSimulated, now);
#endif //def WANT_DEBUG
} else {
properties.setLastEdited(_entity->getLastEdited());
}
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
if (EntityItem::getSendPhysicsUpdates()) {
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
#ifdef WANT_DEBUG
qDebug() << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
#endif
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
} else {
#ifdef WANT_DEBUG
qDebug() << "EntityMotionState::sendUpdate()... NOT sending update as requested.";
#endif
}
// The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them
// to the full set. These flags may be momentarily cleared by incoming external changes.
@ -228,12 +266,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
uint32_t EntityMotionState::getIncomingDirtyFlags() const {
uint32_t dirtyFlags = _entity->getDirtyFlags();
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
int bodyFlags = _body->getCollisionFlags();
bool isMoving = _entity->isMoving();
if (((bodyFlags & btCollisionObject::CF_STATIC_OBJECT) && isMoving) ||
(bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving)) {
dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
if (_body) {
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
int bodyFlags = _body->getCollisionFlags();
bool isMoving = _entity->isMoving();
if (((bodyFlags & btCollisionObject::CF_STATIC_OBJECT) && isMoving) ||
(bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving)) {
dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
}
}
return dirtyFlags;
}

View file

@ -14,7 +14,6 @@
#include <AACube.h>
#include "KinematicController.h"
#include "ObjectMotionState.h"
class EntityItem;
@ -39,8 +38,8 @@ public:
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
MotionType computeMotionType() const;
// virtual override for ObjectMotionState
void addKinematicController();
void updateKinematicState(uint32_t substep);
void stepKinematicSimulation(quint64 now);
// this relays incoming position/rotation to the RigidBody
void getWorldTransform(btTransform& worldTrans) const;
@ -60,6 +59,8 @@ public:
uint32_t getIncomingDirtyFlags() const;
void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); }
EntityItem* getEntity() const { return _entity; }
protected:
EntityItem* _entity;
};

View file

@ -1,22 +0,0 @@
//
// KinematicController.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// 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 "KinematicController.h"
#include "PhysicsEngine.h"
KinematicController::KinematicController() {
_lastFrame = PhysicsEngine::getFrameCount();
}
void KinematicController::start() {
_enabled = true;
_lastFrame = PhysicsEngine::getFrameCount();
}

View file

@ -1,36 +0,0 @@
//
// KinematicController.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// 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_KinematicController_h
#define hifi_KinematicController_h
#include <stdint.h>
/// KinematicController defines an API for derived classes.
class KinematicController {
public:
KinematicController();
virtual ~KinematicController() {}
virtual void stepForward() = 0;
void start();
void stop() { _enabled = false; }
bool isRunning() const { return _enabled; }
protected:
bool _enabled = false;
uint32_t _lastFrame;
};
#endif // hifi_KinematicController_h

View file

@ -12,7 +12,6 @@
#include <math.h>
#include "BulletUtil.h"
#include "KinematicController.h"
#include "ObjectMotionState.h"
#include "PhysicsEngine.h"
@ -56,10 +55,6 @@ ObjectMotionState::ObjectMotionState() :
ObjectMotionState::~ObjectMotionState() {
// NOTE: you MUST remove this MotionState from the world before you call the dtor.
assert(_body == NULL);
if (_kinematicController) {
delete _kinematicController;
_kinematicController = NULL;
}
}
void ObjectMotionState::setFriction(float friction) {
@ -108,6 +103,21 @@ bool ObjectMotionState::doesNotNeedToSendUpdate() const {
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
assert(_body);
// if we've never checked before, our _sentFrame will be 0, and we need to initialize our state
if (_sentFrame == 0) {
_sentPosition = bulletToGLM(_body->getWorldTransform().getOrigin());
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
_sentFrame = simulationFrame;
return false;
}
#ifdef WANT_DEBUG
glm::vec3 wasPosition = _sentPosition;
glm::quat wasRotation = _sentRotation;
glm::vec3 wasAngularVelocity = _sentAngularVelocity;
#endif
float dt = (float)(simulationFrame - _sentFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_sentFrame = simulationFrame;
bool isActive = _body->isActive();
@ -143,11 +153,21 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
float dx2 = glm::distance2(position, _sentPosition);
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
if (dx2 > MAX_POSITION_ERROR_SQUARED) {
#ifdef WANT_DEBUG
qDebug() << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
qDebug() << "wasPosition:" << wasPosition;
qDebug() << "bullet position:" << position;
qDebug() << "_sentPosition:" << _sentPosition;
qDebug() << "dx2:" << dx2;
#endif
return true;
}
if (glm::length2(_sentAngularVelocity) > 0.0f) {
// compute rotation error
_sentAngularVelocity *= powf(1.0f - _angularDamping, dt);
@ -161,12 +181,40 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
}
const float MIN_ROTATION_DOT = 0.98f;
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
#ifdef WANT_DEBUG
if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) {
qDebug() << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ....";
qDebug() << "wasAngularVelocity:" << wasAngularVelocity;
qDebug() << "_sentAngularVelocity:" << _sentAngularVelocity;
qDebug() << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
qDebug() << "length _sentAngularVelocity:" << glm::length(_sentAngularVelocity);
qDebug() << "wasRotation:" << wasRotation;
qDebug() << "bullet actualRotation:" << actualRotation;
qDebug() << "_sentRotation:" << _sentRotation;
}
#endif
return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT);
}
void ObjectMotionState::removeKinematicController() {
if (_kinematicController) {
delete _kinematicController;
_kinematicController = NULL;
void ObjectMotionState::setRigidBody(btRigidBody* body) {
// give the body a (void*) back-pointer to this ObjectMotionState
if (_body != body) {
if (_body) {
_body->setUserPointer(NULL);
}
_body = body;
if (_body) {
_body->setUserPointer(this);
}
}
}
void ObjectMotionState::setKinematic(bool kinematic, uint32_t substep) {
_isKinematic = kinematic;
_lastKinematicSubstep = substep;
}

View file

@ -17,6 +17,7 @@
#include <EntityItem.h>
#include "ContactInfo.h"
#include "ShapeInfo.h"
enum MotionType {
@ -25,6 +26,12 @@ enum MotionType {
MOTION_TYPE_KINEMATIC // keyframed motion
};
enum MotionStateType {
MOTION_STATE_TYPE_UNKNOWN,
MOTION_STATE_TYPE_ENTITY,
MOTION_STATE_TYPE_AVATAR
};
// The update flags trigger two varieties of updates: "hard" which require the body to be pulled
// and re-added to the physics engine and "easy" which just updates the body properties.
const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE);
@ -39,7 +46,6 @@ const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | Entit
class OctreeEditPacketSender;
class KinematicController;
class ObjectMotionState : public btMotionState {
public:
@ -58,6 +64,7 @@ public:
virtual void updateObjectEasy(uint32_t flags, uint32_t frame) = 0;
virtual void updateObjectVelocities() = 0;
MotionStateType getType() const { return _type; }
virtual MotionType getMotionType() const { return _motionType; }
virtual void computeShapeInfo(ShapeInfo& info) = 0;
@ -85,12 +92,22 @@ public:
virtual MotionType computeMotionType() const = 0;
virtual void addKinematicController() = 0;
virtual void removeKinematicController();
virtual void updateKinematicState(uint32_t substep) = 0;
btRigidBody* getRigidBody() const { return _body; }
bool isKinematic() const { return _isKinematic; }
void setKinematic(bool kinematic, uint32_t substep);
virtual void stepKinematicSimulation(quint64 now) = 0;
friend class PhysicsEngine;
protected:
// TODO: move these materials properties to EntityItem
void setRigidBody(btRigidBody* body);
MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN;
// TODO: move these materials properties outside of ObjectMotionState
float _friction;
float _restitution;
float _linearDamping;
@ -98,9 +115,11 @@ protected:
MotionType _motionType;
// _body has NO setters -- it is only changed by PhysicsEngine
btRigidBody* _body;
bool _isKinematic = false;
uint32_t _lastKinematicSubstep = 0;
bool _sentMoving; // true if last update was moving
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
@ -111,8 +130,6 @@ protected:
glm::vec3 _sentVelocity;
glm::vec3 _sentAngularVelocity; // radians per second
glm::vec3 _sentAcceleration;
KinematicController* _kinematicController = NULL;
};
#endif // hifi_ObjectMotionState_h

View file

@ -13,21 +13,15 @@
#include "ShapeInfoUtil.h"
#include "ThreadSafeDynamicsWorld.h"
static uint32_t _frameCount;
static uint32_t _numSubsteps;
// static
uint32_t PhysicsEngine::getFrameCount() {
return _frameCount;
uint32_t PhysicsEngine::getNumSubsteps() {
return _numSubsteps;
}
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
: _collisionConfig(NULL),
_collisionDispatcher(NULL),
_broadphaseFilter(NULL),
_constraintSolver(NULL),
_dynamicsWorld(NULL),
_originOffset(offset),
_entityPacketSender(NULL) {
: _originOffset(offset) {
}
PhysicsEngine::~PhysicsEngine() {
@ -47,8 +41,8 @@ void PhysicsEngine::updateEntitiesInternal(const quint64& now) {
ObjectMotionState* state = *stateItr;
if (state->doesNotNeedToSendUpdate()) {
stateItr = _outgoingPackets.erase(stateItr);
} else if (state->shouldSendUpdate(_frameCount)) {
state->sendUpdate(_entityPacketSender, _frameCount);
} else if (state->shouldSendUpdate(_numSubsteps)) {
state->sendUpdate(_entityPacketSender, _numSubsteps);
++stateItr;
} else {
++stateItr;
@ -68,7 +62,13 @@ void PhysicsEngine::addEntityInternal(EntityItem* entity) {
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
addObject(shapeInfo, shape, motionState);
} else {
} else if (entity->isMoving()) {
EntityMotionState* motionState = new EntityMotionState(entity);
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
motionState->setKinematic(true, _numSubsteps);
_nonPhysicalKinematicObjects.insert(motionState);
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
//qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
}
@ -80,10 +80,16 @@ void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
void* physicsInfo = entity->getPhysicsInfo();
if (physicsInfo) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
removeObject(motionState);
if (motionState->getRigidBody()) {
removeObject(motionState);
} else {
// only need to hunt in this list when there is no RigidBody
_nonPhysicalKinematicObjects.remove(motionState);
}
_entityMotionStates.remove(motionState);
_incomingChanges.remove(motionState);
_outgoingPackets.remove(motionState);
// NOTE: EntityMotionState dtor will remove its backpointer from EntityItem
delete motionState;
}
}
@ -123,6 +129,7 @@ void PhysicsEngine::clearEntitiesInternal() {
delete (*stateItr);
}
_entityMotionStates.clear();
_nonPhysicalKinematicObjects.clear();
_incomingChanges.clear();
_outgoingPackets.clear();
}
@ -133,19 +140,75 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
QSet<ObjectMotionState*>::iterator stateItr = _incomingChanges.begin();
while (stateItr != _incomingChanges.end()) {
ObjectMotionState* motionState = *stateItr;
++stateItr;
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
btRigidBody* body = motionState->_body;
bool removeMotionState = false;
btRigidBody* body = motionState->getRigidBody();
if (body) {
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
// a HARD update requires the body be pulled out of physics engine, changed, then reinserted
// but it also handles all EASY changes
updateObjectHard(body, motionState, flags);
bool success = updateObjectHard(body, motionState, flags);
if (!success) {
// NOTE: since updateObjectHard() failed we know that motionState has been removed
// from simulation and body has been deleted. Depending on what else has changed
// we might need to remove motionState altogether...
if (flags & EntityItem::DIRTY_VELOCITY) {
motionState->updateKinematicState(_numSubsteps);
if (motionState->isKinematic()) {
// all is NOT lost, we still need to move this object around kinematically
_nonPhysicalKinematicObjects.insert(motionState);
} else {
// no need to keep motionState around
removeMotionState = true;
}
} else {
// no need to keep motionState around
removeMotionState = true;
}
}
} else if (flags) {
// an EASY update does NOT require that the body be pulled out of physics engine
// hence the MotionState has all the knowledge and authority to perform the update.
motionState->updateObjectEasy(flags, _frameCount);
motionState->updateObjectEasy(flags, _numSubsteps);
}
} else {
// the only way we should ever get here (motionState exists but no body) is when the object
// is undergoing non-physical kinematic motion.
assert(_nonPhysicalKinematicObjects.contains(motionState));
// it is possible that the changes are such that the object can now be added to the physical simulation
if (flags & EntityItem::DIRTY_SHAPE) {
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
if (shape) {
addObject(shapeInfo, shape, motionState);
_nonPhysicalKinematicObjects.remove(motionState);
} else if (flags & EntityItem::DIRTY_VELOCITY) {
// although we couldn't add the object to the simulation, might need to update kinematic motion...
motionState->updateKinematicState(_numSubsteps);
if (!motionState->isKinematic()) {
_nonPhysicalKinematicObjects.remove(motionState);
removeMotionState = true;
}
}
} else if (flags & EntityItem::DIRTY_VELOCITY) {
// although we still can't add to physics simulation, might need to update kinematic motion...
motionState->updateKinematicState(_numSubsteps);
if (!motionState->isKinematic()) {
_nonPhysicalKinematicObjects.remove(motionState);
removeMotionState = true;
}
}
}
if (removeMotionState) {
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
_outgoingPackets.remove(motionState);
// NOTE: motionState will clean up its own backpointers in the Object
delete motionState;
continue;
}
// NOTE: the grand order of operations is:
@ -158,11 +221,24 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
// outgoing changes at this point.
motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped
motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed
++stateItr;
}
_incomingChanges.clear();
}
void PhysicsEngine::removeContacts(ObjectMotionState* motionState) {
// trigger events for new/existing/old contacts
ContactMap::iterator contactItr = _contactMap.begin();
while (contactItr != _contactMap.end()) {
if (contactItr->first._a == motionState || contactItr->first._b == motionState) {
ContactMap::iterator iterToDelete = contactItr;
++contactItr;
_contactMap.erase(iterToDelete);
} else {
++contactItr;
}
}
}
// virtual
void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
// _entityTree should be set prior to the init() call
@ -178,22 +254,6 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
// default gravity of the world is zero, so each object must specify its own gravity
// TODO: set up gravity zones
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
// GROUND HACK: add a big planar floor (and walls for testing) to catch falling objects
btTransform groundTransform;
groundTransform.setIdentity();
for (int i = 0; i < 3; ++i) {
btVector3 normal(0.0f, 0.0f, 0.0f);
normal[i] = 1.0f;
btCollisionShape* plane = new btStaticPlaneShape(normal, 0.0f);
btCollisionObject* groundObject = new btCollisionObject();
groundObject->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
groundObject->setCollisionShape(plane);
groundObject->setWorldTransform(groundTransform);
_dynamicsWorld->addCollisionObject(groundObject);
}
}
assert(packetSender);
@ -219,23 +279,115 @@ void PhysicsEngine::stepSimulation() {
float timeStep = btMin(dt, MAX_TIMESTEP);
// This is step (2).
int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_frameCount += (uint32_t)numSubSteps;
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_numSubsteps += (uint32_t)numSubsteps;
stepNonPhysicalKinematics(usecTimestampNow());
unlock();
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
if (numSubsteps > 0) {
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
//
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
// lock on the tree before we re-lock ourselves.
//
// TODO: untangle these lock sequences.
_entityTree->lockForWrite();
lock();
_dynamicsWorld->synchronizeMotionStates();
unlock();
_entityTree->unlock();
computeCollisionEvents();
}
}
void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) {
QSet<ObjectMotionState*>::iterator stateItr = _nonPhysicalKinematicObjects.begin();
while (stateItr != _nonPhysicalKinematicObjects.end()) {
ObjectMotionState* motionState = *stateItr;
motionState->stepKinematicSimulation(now);
++stateItr;
}
}
// TODO?: need to occasionally scan for stopped non-physical kinematics objects
void PhysicsEngine::computeCollisionEvents() {
// update all contacts every frame
int numManifolds = _collisionDispatcher->getNumManifolds();
for (int i = 0; i < numManifolds; ++i) {
btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i);
if (contactManifold->getNumContacts() > 0) {
// TODO: require scripts to register interest in callbacks for specific objects
// so we can filter out most collision events right here.
const btCollisionObject* objectA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
const btCollisionObject* objectB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
if (!(objectA->isActive() || objectB->isActive())) {
// both objects are inactive so stop tracking this contact,
// which will eventually trigger a CONTACT_EVENT_TYPE_END
continue;
}
void* a = objectA->getUserPointer();
void* b = objectB->getUserPointer();
if (a || b) {
// the manifold has up to 4 distinct points, but only extract info from the first
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0), _originOffset);
}
}
}
// We harvest collision callbacks every few frames, which contributes the following effects:
//
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
// lock on the tree before we re-lock ourselves.
// (1) There is a maximum collision callback rate per pair: substep_rate / SUBSTEPS_PER_COLLIION_FRAME
// (2) END/START cycles shorter than SUBSTEPS_PER_COLLIION_FRAME will be filtered out
// (3) There is variable lag between when the contact actually starts and when it is reported,
// up to SUBSTEPS_PER_COLLIION_FRAME * time_per_substep
//
// TODO: untangle these lock sequences.
_entityTree->lockForWrite();
lock();
_dynamicsWorld->synchronizeMotionStates();
unlock();
_entityTree->unlock();
const uint32_t SUBSTEPS_PER_COLLISION_FRAME = 2;
if (_numSubsteps - _numContactFrames * SUBSTEPS_PER_COLLISION_FRAME < SUBSTEPS_PER_COLLISION_FRAME) {
// we don't harvest collision callbacks every frame
// this sets a maximum callback-per-contact rate
// and also filters out END/START events that happen on shorter timescales
return;
}
++_numContactFrames;
// scan known contacts and trigger events
ContactMap::iterator contactItr = _contactMap.begin();
while (contactItr != _contactMap.end()) {
ObjectMotionState* A = static_cast<ObjectMotionState*>(contactItr->first._a);
ObjectMotionState* B = static_cast<ObjectMotionState*>(contactItr->first._b);
// TODO: make triggering these events clean and efficient. The code at this context shouldn't
// have to figure out what kind of object (entity, avatar, etc) these are in order to properly
// emit a collision event.
if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) {
EntityItemID idA = static_cast<EntityMotionState*>(A)->getEntity()->getEntityItemID();
EntityItemID idB;
if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
}
emit entityCollisionWithEntity(idA, idB, contactItr->second);
} else if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
EntityItemID idA;
EntityItemID idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
emit entityCollisionWithEntity(idA, idB, contactItr->second);
}
// TODO: enable scripts to filter based on contact event type
ContactEventType type = contactItr->second.computeType(_numContactFrames);
if (type == CONTACT_EVENT_TYPE_END) {
ContactMap::iterator iterToDelete = contactItr;
++contactItr;
_contactMap.erase(iterToDelete);
} else {
++contactItr;
}
}
}
// Bullet collision flags are as follows:
@ -259,8 +411,8 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
body->updateInertiaTensor();
motionState->_body = body;
motionState->addKinematicController();
motionState->setRigidBody(body);
motionState->setKinematic(true, _numSubsteps);
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
@ -271,7 +423,8 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
shape->calculateLocalInertia(mass, inertia);
body = new btRigidBody(mass, motionState, shape, inertia);
body->updateInertiaTensor();
motionState->_body = body;
motionState->setRigidBody(body);
motionState->setKinematic(false, _numSubsteps);
motionState->updateObjectVelocities();
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
@ -285,7 +438,8 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
body->updateInertiaTensor();
motionState->_body = body;
motionState->setRigidBody(body);
motionState->setKinematic(false, _numSubsteps);
break;
}
}
@ -296,9 +450,9 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
_dynamicsWorld->addRigidBody(body);
}
bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
void PhysicsEngine::removeObject(ObjectMotionState* motionState) {
assert(motionState);
btRigidBody* body = motionState->_body;
btRigidBody* body = motionState->getRigidBody();
if (body) {
const btCollisionShape* shape = body->getCollisionShape();
ShapeInfo shapeInfo;
@ -306,15 +460,15 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
_dynamicsWorld->removeRigidBody(body);
_shapeManager.releaseShape(shapeInfo);
delete body;
motionState->_body = NULL;
motionState->removeKinematicController();
return true;
motionState->setRigidBody(NULL);
motionState->setKinematic(false, _numSubsteps);
removeContacts(motionState);
}
return false;
}
// private
void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
MotionType newType = motionState->computeMotionType();
// pull body out of physics engine
@ -329,7 +483,16 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
btCollisionShape* newShape = _shapeManager.getShape(shapeInfo);
if (newShape != oldShape) {
if (!newShape) {
// FAIL! we are unable to support these changes!
_shapeManager.releaseShape(oldShape);
delete body;
motionState->setRigidBody(NULL);
motionState->setKinematic(false, _numSubsteps);
removeContacts(motionState);
return false;
} else if (newShape != oldShape) {
// BUG: if shape doesn't change but density does then we won't compute new mass properties
// TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly.
body->setCollisionShape(newShape);
@ -349,7 +512,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
}
bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS;
if (easyUpdate) {
motionState->updateObjectEasy(flags, _frameCount);
motionState->updateObjectEasy(flags, _numSubsteps);
}
// update the motion parameters
@ -362,7 +525,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
body->updateInertiaTensor();
motionState->addKinematicController();
motionState->setKinematic(true, _numSubsteps);
break;
}
case MOTION_TYPE_DYNAMIC: {
@ -379,7 +542,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
body->updateInertiaTensor();
}
body->forceActivationState(ACTIVE_TAG);
motionState->removeKinematicController();
motionState->setKinematic(false, _numSubsteps);
break;
}
default: {
@ -394,7 +557,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f));
motionState->removeKinematicController();
motionState->setKinematic(false, _numSubsteps);
break;
}
}
@ -403,4 +566,5 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
_dynamicsWorld->addRigidBody(body);
body->activate();
return true;
}

View file

@ -23,6 +23,7 @@ const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f;
#include <EntitySimulation.h>
#include "BulletUtil.h"
#include "ContactInfo.h"
#include "EntityMotionState.h"
#include "ShapeManager.h"
#include "ThreadSafeDynamicsWorld.h"
@ -31,10 +32,26 @@ const float HALF_SIMULATION_EXTENT = 512.0f; // meters
class ObjectMotionState;
// simple class for keeping track of contacts
class ContactKey {
public:
ContactKey() = delete;
ContactKey(void* a, void* b) : _a(a), _b(b) {}
bool operator<(const ContactKey& other) const { return _a < other._a || (_a == other._a && _b < other._b); }
bool operator==(const ContactKey& other) const { return _a == other._a && _b == other._b; }
void* _a;
void* _b;
};
typedef std::map<ContactKey, ContactInfo> ContactMap;
typedef std::pair<ContactKey, ContactInfo> ContactMapElement;
class PhysicsEngine : public EntitySimulation {
public:
static uint32_t getFrameCount();
// TODO: find a good way to make this a non-static method
static uint32_t getNumSubsteps();
PhysicsEngine() = delete; // prevent compiler from creating default ctor
PhysicsEngine(const glm::vec3& offset);
~PhysicsEngine();
@ -50,6 +67,9 @@ public:
virtual void init(EntityEditPacketSender* packetSender);
void stepSimulation();
void stepNonPhysicalKinematics(const quint64& now);
void computeCollisionEvents();
/// \param offset position of simulation origin in domain-frame
void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; }
@ -62,36 +82,38 @@ public:
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
/// \param motionState pointer to Object's MotionState
/// \return true if Object removed
bool removeObject(ObjectMotionState* motionState);
void removeObject(ObjectMotionState* motionState);
/// process queue of changed from external sources
void relayIncomingChangesToSimulation();
/// \return duration of fixed simulation substep
float getFixedSubStep() const;
private:
void removeContacts(ObjectMotionState* motionState);
protected:
void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
// return 'true' of update was successful
bool updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
btClock _clock;
btDefaultCollisionConfiguration* _collisionConfig;
btCollisionDispatcher* _collisionDispatcher;
btBroadphaseInterface* _broadphaseFilter;
btSequentialImpulseConstraintSolver* _constraintSolver;
ThreadSafeDynamicsWorld* _dynamicsWorld;
btDefaultCollisionConfiguration* _collisionConfig = NULL;
btCollisionDispatcher* _collisionDispatcher = NULL;
btBroadphaseInterface* _broadphaseFilter = NULL;
btSequentialImpulseConstraintSolver* _constraintSolver = NULL;
ThreadSafeDynamicsWorld* _dynamicsWorld = NULL;
ShapeManager _shapeManager;
private:
glm::vec3 _originOffset;
// EntitySimulation stuff
QSet<EntityMotionState*> _entityMotionStates; // all entities that we track
QSet<ObjectMotionState*> _nonPhysicalKinematicObjects; // not in physics simulation, but still need kinematic simulation
QSet<ObjectMotionState*> _incomingChanges; // entities with pending physics changes by script or packet
QSet<ObjectMotionState*> _outgoingPackets; // MotionStates with pending changes that need to be sent over wire
EntityEditPacketSender* _entityPacketSender;
EntityEditPacketSender* _entityPacketSender = NULL;
ContactMap _contactMap;
uint32_t _numContactFrames = 0;
};
#endif // hifi_PhysicsEngine_h

View file

@ -1,21 +0,0 @@
//
// SimpleEntityKinematicController.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// 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 "PhysicsEngine.h"
#include "SimpleEntityKinematicController.h"
void SimpleEntityKinematicController:: stepForward() {
uint32_t frame = PhysicsEngine::getFrameCount();
float dt = (frame - _lastFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_entity->simulateSimpleKinematicMotion(dt);
_lastFrame = frame;
}

View file

@ -1,38 +0,0 @@
//
// SimpleEntityKinematicController.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// 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_SimpleEntityKinematicController_h
#define hifi_SimpleEntityKinematicController_h
/// SimpleKinematicConstroller performs simple exrapolation of velocities.
#include <assert.h>
#include <glm/glm.hpp>
#include <EntityItem.h>
#include "KinematicController.h"
class SimpleEntityKinematicController : public KinematicController {
public:
SimpleEntityKinematicController() = delete; // prevent compiler from making a default ctor
SimpleEntityKinematicController(EntityItem* entity) : KinematicController(), _entity(entity) { assert(entity); }
~SimpleEntityKinematicController() { _entity = NULL; }
void stepForward();
private:
EntityItem* _entity;
};
#endif // hifi_SimpleEntityKinematicController_h

View file

@ -74,6 +74,7 @@ void AnimationHandle::setRunning(bool running) {
}
} else {
_model->_runningAnimations.removeOne(_self);
restoreJoints();
replaceMatchingPriorities(0.0f);
}
emit runningChanged(isRunning());
@ -173,3 +174,13 @@ void AnimationHandle::replaceMatchingPriorities(float newPriority) {
}
}
void AnimationHandle::restoreJoints() {
for (int i = 0; i < _jointMappings.size(); i++) {
int mapping = _jointMappings.at(i);
if (mapping != -1) {
JointState& state = _model->_jointStates[mapping];
state.restoreRotation(1.0f, state._animationPriority);
}
}
}

View file

@ -92,6 +92,7 @@ private:
void simulate(float deltaTime);
void applyFrame(float frameIndex);
void replaceMatchingPriorities(float newPriority);
void restoreJoints();
Model* _model;
WeakAnimationHandlePointer _self;

View file

@ -385,7 +385,9 @@ Texture::~Texture() {
NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) :
Resource(url, !content.isEmpty()),
_type(type),
_translucent(false) {
_translucent(false),
_width(0),
_height(0) {
if (!url.isValid()) {
_loaded = true;
@ -457,6 +459,9 @@ void ImageReader::run() {
_reply->deleteLater();
}
QImage image = QImage::fromData(_content);
int originalWidth = image.width();
int originalHeight = image.height();
// enforce a fixed maximum
const int MAXIMUM_SIZE = 1024;
@ -485,7 +490,7 @@ void ImageReader::run() {
averageColor.setRgb(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea);
}
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false),
Q_ARG(const QColor&, averageColor));
Q_ARG(const QColor&, averageColor), Q_ARG(int, originalWidth), Q_ARG(int, originalHeight));
return;
}
if (image.format() != QImage::Format_ARGB32) {
@ -517,7 +522,8 @@ void ImageReader::run() {
}
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
Q_ARG(bool, translucentPixels >= imageArea / 2), Q_ARG(const QColor&, QColor(redTotal / imageArea,
greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea)));
greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea)),
Q_ARG(int, originalWidth), Q_ARG(int, originalHeight));
}
void NetworkTexture::downloadFinished(QNetworkReply* reply) {
@ -529,9 +535,14 @@ void NetworkTexture::loadContent(const QByteArray& content) {
QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content));
}
void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor) {
void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor, int originalWidth,
int originalHeight) {
_translucent = translucent;
_averageColor = averageColor;
_originalWidth = originalWidth;
_originalHeight = originalHeight;
_width = image.width();
_height = image.height();
finishedLoading(true);
imageLoaded(image);

View file

@ -150,12 +150,18 @@ public:
/// Returns the lazily-computed average texture color.
const QColor& getAverageColor() const { return _averageColor; }
int getOriginalWidth() const { return _originalWidth; }
int getOriginalHeight() const { return _originalHeight; }
int getWidth() const { return _width; }
int getHeight() const { return _height; }
protected:
virtual void downloadFinished(QNetworkReply* reply);
Q_INVOKABLE void loadContent(const QByteArray& content);
Q_INVOKABLE void setImage(const QImage& image, bool translucent, const QColor& averageColor);
Q_INVOKABLE void setImage(const QImage& image, bool translucent, const QColor& averageColor, int originalWidth,
int originalHeight);
virtual void imageLoaded(const QImage& image);
@ -163,6 +169,10 @@ private:
TextureType _type;
bool _translucent;
QColor _averageColor;
int _originalWidth;
int _originalHeight;
int _width;
int _height;
};
/// Caches derived, dilated textures.