diff --git a/examples/example/games/sword.js b/examples/example/games/sword.js
index 66503b62aa..18d6911f0b 100644
--- a/examples/example/games/sword.js
+++ b/examples/example/games/sword.js
@@ -11,24 +11,26 @@
//
"use strict";
/*jslint vars: true*/
-var Script, Entities, MyAvatar, Window, Overlays, Controller, Vec3, Quat, print, ToolBar; // Referenced globals provided by High Fidelity.
-Script.include(["../../libraries/toolBars.js"]);
+var Script, Entities, MyAvatar, Window, Overlays, Controller, Vec3, Quat, print, ToolBar, Settings; // Referenced globals provided by High Fidelity.
+Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.js");
-var hand = "right";
+var hand = Settings.getValue("highfidelity.sword.hand", "right");
var nullActionID = "00000000-0000-0000-0000-000000000000";
var controllerID;
var controllerActive;
var stickID = null;
var actionID = nullActionID;
var targetIDs = [];
-var dimensions = { x: 0.3, y: 0.1, z: 2.0 };
-var AWAY_ORIENTATION = Quat.fromPitchYawRollDegrees(-90, 0, 0);
+var dimensions = { x: 0.3, y: 0.15, z: 2.0 };
var BUTTON_SIZE = 32;
var stickModel = "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx";
var swordModel = "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx";
+var swordCollisionShape = "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.obj";
+var swordCollisionSoundURL = "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/swordStrike1.wav";
+var avatarCollisionSoundURL = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav";
var whichModel = "sword";
-var attachmentOffset, MOUSE_CONTROLLER_OFFSET = {x: 0.5, y: 0.4, z: 0.0}; // A fudge when using mouse rather than hand-controller, to hit yourself less often.
+var originalAvatarCollisionSound;
var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.sword.toolbar", function () {
return {x: 100, y: 380};
@@ -37,6 +39,7 @@ var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.sword.toolbar",
var SWORD_IMAGE = "http://s3.amazonaws.com/hifi-public/images/billiardsReticle.png"; // Toggle between brandishing/sheathing sword (creating if necessary)
var TARGET_IMAGE = "http://s3.amazonaws.com/hifi-public/images/puck.png"; // Create a target dummy
var CLEANUP_IMAGE = "http://s3.amazonaws.com/hifi-public/images/delete.png"; // Remove sword and all target dummies.f
+var SWITCH_HANDS_IMAGE = "http://s3.amazonaws.com/hifi-public/images/up-arrow.svg"; // Toggle left vs right hand. Persists in settings.
var swordButton = toolBar.addOverlay("image", {
width: BUTTON_SIZE,
height: BUTTON_SIZE,
@@ -49,6 +52,12 @@ var targetButton = toolBar.addOverlay("image", {
imageURL: TARGET_IMAGE,
alpha: 1
});
+var switchHandsButton = toolBar.addOverlay("image", {
+ width: BUTTON_SIZE,
+ height: BUTTON_SIZE,
+ imageURL: SWITCH_HANDS_IMAGE,
+ alpha: 1
+});
var cleanupButton = toolBar.addOverlay("image", {
width: BUTTON_SIZE,
height: BUTTON_SIZE,
@@ -77,53 +86,51 @@ function flash(color) {
flasher.timer = Script.setTimeout(clearFlash, 500);
}
-
var health = 100;
-var display;
-var isAway = false;
+var display2d, display3d;
+function trackAvatarWithText() {
+ Entities.editEntity(display3d, {
+ position: Vec3.sum(MyAvatar.position, {x: 0, y: 1.5, z: 0}),
+ rotation: Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollDegrees(0, 180, 0))
+ });
+}
function updateDisplay() {
var text = health.toString();
- if (!display) {
+ if (!display2d) {
health = 100;
- display = Overlays.addOverlay("text", {
+ display2d = Overlays.addOverlay("text", {
text: text,
font: { size: 20 },
color: {red: 0, green: 255, blue: 0},
backgroundColor: {red: 100, green: 100, blue: 100}, // Why doesn't this and the next work?
backgroundAlpha: 0.9,
- x: Window.innerWidth - 50,
- y: 50
+ x: toolBar.x - 5, // I'd like to add the score to the toolBar and have it drag with it, but toolBar doesn't support text (just buttons).
+ y: toolBar.y - 30 // So next best thing is to position it each time as if it were on top.
});
+ display3d = Entities.addEntity({
+ name: MyAvatar.displayName + " score",
+ textColor: {red: 255, green: 255, blue: 255},
+ type: "Text",
+ text: text,
+ lineHeight: 0.14,
+ backgroundColor: {red: 64, green: 64, blue: 64},
+ dimensions: {x: 0.3, y: 0.2, z: 0.01},
+ });
+ Script.update.connect(trackAvatarWithText);
} else {
- Overlays.editOverlay(display, {text: text});
+ Overlays.editOverlay(display2d, {text: text});
+ Entities.editEntity(display3d, {text: text});
}
}
function removeDisplay() {
- if (display) {
- Overlays.deleteOverlay(display);
- display = null;
+ if (display2d) {
+ Overlays.deleteOverlay(display2d);
+ display2d = null;
+ Script.update.disconnect(trackAvatarWithText);
+ Entities.deleteEntity(display3d);
+ display3d = null;
}
}
-
-function cleanUp(leaveButtons) {
- attachmentOffset = {x: 0, y: 0, z: 0};
- if (stickID) {
- Entities.deleteAction(stickID, actionID);
- Entities.deleteEntity(stickID);
- stickID = null;
- actionID = null;
- }
- targetIDs.forEach(function (id) {
- Entities.deleteAction(id.entity, id.action);
- Entities.deleteEntity(id.entity);
- });
- targetIDs = [];
- removeDisplay();
- if (!leaveButtons) {
- toolBar.cleanup();
- }
-}
-
function computeEnergy(collision, entityID) {
var id = entityID || collision.idA || collision.idB;
var entity = id && Entities.getEntityProperties(id);
@@ -133,31 +140,67 @@ function computeEnergy(collision, entityID) {
return Math.min(Math.max(1.0, Math.round(energy)), 20);
}
function gotHit(collision) {
- if (isAway) { return; }
var energy = computeEnergy(collision);
+ print("Got hit - " + energy + " from " + collision.idA + " " + collision.idB);
health -= energy;
flash({red: 255, green: 0, blue: 0});
updateDisplay();
}
function scoreHit(idA, idB, collision) {
- if (isAway) { return; }
var energy = computeEnergy(collision, idA);
+ print("Score + " + energy + " from " + JSON.stringify(idA) + " " + JSON.stringify(idB));
health += energy;
flash({red: 0, green: 255, blue: 0});
updateDisplay();
}
-function positionStick(stickOrientation) {
- var baseOffset = Vec3.sum(attachmentOffset, {x: 0.0, y: 0.0, z: -dimensions.z / 2});
- var offset = Vec3.multiplyQbyV(stickOrientation, baseOffset);
- Entities.updateAction(stickID, actionID, {relativePosition: offset,
- relativeRotation: stickOrientation});
+function isFighting() {
+ return stickID && (actionID !== nullActionID);
}
-
+function initControls() {
+ print("Sword hand is " + hand);
+ if (hand === "right") {
+ controllerID = 3; // right handed
+ } else {
+ controllerID = 4; // left handed
+ }
+}
+var inHand = false;
+function positionStick(stickOrientation) {
+ var reorient = Quat.fromPitchYawRollDegrees(0, -90, 0);
+ var baseOffset = {x: -dimensions.z * 0.8, y: 0, z: 0};
+ var offset = Vec3.multiplyQbyV(reorient, baseOffset);
+ stickOrientation = Quat.multiply(reorient, stickOrientation);
+ inHand = false;
+ Entities.updateAction(stickID, actionID, {
+ relativePosition: offset,
+ relativeRotation: stickOrientation
+ });
+}
+function resetToHand() { // Maybe coordinate with positionStick?
+ if (inHand) { // Optimization: bail if we're already inHand.
+ return;
+ }
+ print('Reset to hand');
+ Entities.updateAction(stickID, actionID, {
+ relativePosition: {x: 0.0, y: 0.0, z: -dimensions.z * 0.5},
+ relativeRotation: Quat.fromVec3Degrees({x: 45.0, y: 0.0, z: 0.0}),
+ hand: hand, // It should not be necessary to repeat these two, but there seems to be a bug in that that
+ timeScale: 0.05 // they do not retain their earlier values if you don't repeat them.
+ });
+ inHand = true;
+}
function mouseMoveEvent(event) {
- attachmentOffset = MOUSE_CONTROLLER_OFFSET;
- if (!stickID || actionID === nullActionID || isAway) {
+ if (event.deviceID) { // Not a MOUSE mouse event, but a (e.g., hydra) mouse event, with x/y that is not meaningful for us.
+ resetToHand(); // Can only happen when controller is uncradled, so let's drive with that, resetting our attachement.
+ return;
+ }
+ controllerActive = (Vec3.length(Controller.getSpatialControlPosition(controllerID)) > 0);
+ //print("Mouse move with hand controller " + (controllerActive ? "active" : "inactive") + JSON.stringify(event));
+ if (controllerActive || !isFighting()) {
+ print('Attempting attachment reset');
+ resetToHand();
return;
}
var windowCenterX = Window.innerWidth / 2;
@@ -167,73 +210,80 @@ function mouseMoveEvent(event) {
var mouseXRatio = mouseXCenterOffset / windowCenterX;
var mouseYRatio = mouseYCenterOffset / windowCenterY;
- var stickOrientation = Quat.fromPitchYawRollDegrees(mouseYRatio * -90, mouseXRatio * -90, 0);
+ var stickOrientation = Quat.fromPitchYawRollDegrees(mouseYRatio * 90, mouseXRatio * 90, 0);
positionStick(stickOrientation);
}
-
-function initControls() {
- if (hand === "right") {
- controllerID = 3; // right handed
- } else {
- controllerID = 4; // left handed
+function removeSword() {
+ if (stickID) {
+ print('deleting action ' + actionID + ' and entity ' + stickID);
+ Entities.deleteAction(stickID, actionID);
+ Entities.deleteEntity(stickID);
+ stickID = null;
+ actionID = nullActionID;
+ Controller.mouseMoveEvent.disconnect(mouseMoveEvent);
+ MyAvatar.collisionWithEntity.disconnect(gotHit);
+ // removeEventhHandler happens automatically when the entity is deleted.
+ }
+ inHand = false;
+ if (originalAvatarCollisionSound !== undefined) {
+ MyAvatar.collisionSoundURL = originalAvatarCollisionSound;
+ }
+ removeDisplay();
+}
+function cleanUp(leaveButtons) {
+ removeSword();
+ targetIDs.forEach(function (id) {
+ Entities.deleteAction(id.entity, id.action);
+ Entities.deleteEntity(id.entity);
+ });
+ targetIDs = [];
+ if (!leaveButtons) {
+ toolBar.cleanup();
}
}
-
-
-function update() {
- var palmPosition = Controller.getSpatialControlPosition(controllerID);
- controllerActive = (Vec3.length(palmPosition) > 0);
- if (!controllerActive) {
- return;
+function makeSword() {
+ initControls();
+ stickID = Entities.addEntity({
+ type: "Model",
+ modelURL: swordModel,
+ compoundShapeURL: swordCollisionShape,
+ dimensions: dimensions,
+ position: (hand === 'right') ? MyAvatar.getRightPalmPosition() : MyAvatar.getLeftPalmPosition(), // initial position doesn't matter, as long as it's close
+ rotation: MyAvatar.orientation,
+ damping: 0.1,
+ collisionSoundURL: swordCollisionSoundURL,
+ restitution: 0.01,
+ collisionsWillMove: true
+ });
+ actionID = Entities.addAction("hold", stickID, {
+ relativePosition: {x: 0.0, y: 0.0, z: -dimensions.z * 0.5},
+ relativeRotation: Quat.fromVec3Degrees({x: 45.0, y: 0.0, z: 0.0}),
+ hand: hand,
+ timeScale: 0.05
+ });
+ if (actionID === nullActionID) {
+ print('*** FAILED TO MAKE SWORD ACTION ***');
+ cleanUp();
}
-
- var stickOrientation = Controller.getSpatialControlRawRotation(controllerID);
- var adjustment = Quat.fromPitchYawRollDegrees(180, 0, 0);
- stickOrientation = Quat.multiply(stickOrientation, adjustment);
-
- positionStick(stickOrientation);
-}
-
-function toggleAway() {
- isAway = !isAway;
- if (isAway) {
- positionStick(AWAY_ORIENTATION);
- removeDisplay();
- } else {
- updateDisplay();
+ if (originalAvatarCollisionSound === undefined) {
+ originalAvatarCollisionSound = MyAvatar.collisionSoundURL; // We won't get MyAvatar.collisionWithEntity unless there's a sound URL. (Bug.)
+ SoundCache.getSound(avatarCollisionSoundURL); // Interface does not currently "preload" this? (Bug?)
}
+ MyAvatar.collisionSoundURL = avatarCollisionSoundURL;
+ Controller.mouseMoveEvent.connect(mouseMoveEvent);
+ MyAvatar.collisionWithEntity.connect(gotHit);
+ Script.addEventHandler(stickID, 'collisionWithEntity', scoreHit);
+ updateDisplay();
}
function onClick(event) {
switch (Overlays.getOverlayAtPoint(event)) {
case swordButton:
if (!stickID) {
- initControls();
- stickID = Entities.addEntity({
- type: "Model",
- modelURL: (whichModel === "sword") ? swordModel : stickModel,
- //compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj",
- shapeType: "box",
- dimensions: dimensions,
- position: MyAvatar.getRightPalmPosition(), // initial position doesn't matter, as long as it's close
- rotation: MyAvatar.orientation,
- damping: 0.1,
- collisionSoundURL: "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/swordStrike1.wav",
- restitution: 0.01,
- collisionsWillMove: true
- });
- actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -dimensions.z / 2},
- hand: hand,
- timeScale: 0.15});
- if (actionID === nullActionID) {
- print('*** FAILED TO MAKE SWORD ACTION ***');
- cleanUp();
- }
- Script.addEventHandler(stickID, 'collisionWithEntity', scoreHit);
- updateDisplay();
+ makeSword();
} else {
- toggleAway();
+ removeSword();
}
break;
case targetButton:
@@ -256,6 +306,12 @@ function onClick(event) {
});
targetIDs.push({entity: boxId, action: action});
break;
+ case switchHandsButton:
+ cleanUp('leaveButtons');
+ hand = hand === "right" ? "left" : "right";
+ Settings.setValue("highfidelity.sword.hand", hand);
+ makeSword();
+ break;
case cleanupButton:
cleanUp('leaveButtons');
break;
@@ -263,7 +319,4 @@ function onClick(event) {
}
Script.scriptEnding.connect(cleanUp);
-Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(onClick);
-Script.update.connect(update);
-MyAvatar.collisionWithEntity.connect(gotHit);
diff --git a/examples/voxels.js b/examples/voxels.js
index 799af04bef..e110f15260 100644
--- a/examples/voxels.js
+++ b/examples/voxels.js
@@ -2,6 +2,32 @@ var controlHeld = false;
var shiftHeld = false;
+function attemptVoxelChange(intersection) {
+ var ids = Entities.findEntities(intersection.intersection, 10);
+ var success = false;
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i];
+ if (controlHeld) {
+ // hold control to erase a sphere
+ if (Entities.setVoxelSphere(id, intersection.intersection, 1.0, 0)) {
+ success = true;
+ }
+ } else if (shiftHeld) {
+ // hold shift to set all voxels to 255
+ if (Entities.setAllVoxels(id, 255)) {
+ success = true;
+ }
+ } else {
+ // no modifier key means to add a sphere
+ if (Entities.setVoxelSphere(id, intersection.intersection, 1.0, 255)) {
+ success = true;
+ }
+ }
+ }
+ return success;
+}
+
+
function mousePressEvent(event) {
if (!event.isLeftButton) {
return;
@@ -9,20 +35,21 @@ function mousePressEvent(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking
- // var props = Entities.getEntityProperties(intersection.entityID);
+
+ // we've used a picking ray to decide where to add the new sphere of voxels. If we pick nothing
+ // or if we pick a non-PolyVox entity, we fall through to the next picking attempt.
if (intersection.intersects) {
- var ids = Entities.findEntities(intersection.intersection, 10);
- for (var i = 0; i < ids.length; i++) {
- var id = ids[i];
- if (controlHeld) {
- Entities.setVoxelSphere(id, intersection.intersection, 1.0, 0);
- } else if (shiftHeld) {
- Entities.setAllVoxels(id, 255);
- } else {
- Entities.setVoxelSphere(id, intersection.intersection, 1.0, 255);
- }
+ if (attemptVoxelChange(intersection)) {
+ return;
}
}
+
+ // if the PolyVox entity is empty, we can't pick against its voxel. try picking against its
+ // bounding box, instead.
+ intersection = Entities.findRayIntersection(pickRay, false); // bounding box picking
+ if (intersection.intersects) {
+ attemptVoxelChange(intersection);
+ }
}
diff --git a/interface/resources/images/interface-logo.svg b/interface/resources/images/interface-logo.svg
new file mode 100644
index 0000000000..61fc9d9afb
--- /dev/null
+++ b/interface/resources/images/interface-logo.svg
@@ -0,0 +1,34 @@
+
+
+
diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml
index 8d5267f7f8..29264fa608 100644
--- a/interface/resources/qml/LoginDialog.qml
+++ b/interface/resources/qml/LoginDialog.qml
@@ -41,8 +41,6 @@ DialogContainer {
readonly property int closeMargin: 16
readonly property real tan30: 0.577 // tan(30°)
readonly property int inputSpacing: 16
- property int maximumX: parent ? parent.width - width : 0
- property int maximumY: parent ? parent.height - height : 0
Rectangle {
id: backgroundRectangle
diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml
index e5216ff619..8baf41cd75 100644
--- a/interface/resources/qml/UpdateDialog.qml
+++ b/interface/resources/qml/UpdateDialog.qml
@@ -1,164 +1,176 @@
import Hifi 1.0
import QtQuick 2.3
+import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import "controls"
import "styles"
DialogContainer {
- HifiConstants { id: hifi }
id: root
+ HifiConstants { id: hifi }
+
objectName: "UpdateDialog"
- implicitWidth: updateDialog.width
- implicitHeight: updateDialog.height
+
+ implicitWidth: updateDialog.implicitWidth
+ implicitHeight: updateDialog.implicitHeight
+
x: parent ? parent.width / 2 - width / 2 : 0
y: parent ? parent.height / 2 - height / 2 : 0
-
+ property int maximumX: parent ? parent.width - width : 0
+ property int maximumY: parent ? parent.height - height : 0
+
UpdateDialog {
id: updateDialog
implicitWidth: backgroundRectangle.width
implicitHeight: backgroundRectangle.height
- readonly property int inputWidth: 500
- readonly property int inputHeight: 60
+ readonly property int contentWidth: 500
+ readonly property int logoSize: 60
readonly property int borderWidth: 30
readonly property int closeMargin: 16
readonly property int inputSpacing: 16
- readonly property int buttonWidth: 150
- readonly property int buttonHeight: 50
- readonly property int buttonRadius: 15
-
+ readonly property int buttonWidth: 100
+ readonly property int buttonHeight: 30
+ readonly property int noticeHeight: 15 * inputSpacing
+ readonly property string fontFamily: Qt.platform.os === "windows" ? "Trebuchet MS" : "Trebuchet"
+
signal triggerBuildDownload
signal closeUpdateDialog
+ Rectangle {
+ id: backgroundRectangle
+ color: "#ffffff"
+
+ width: updateDialog.contentWidth + updateDialog.borderWidth * 2
+ height: mainContent.height + updateDialog.borderWidth * 2 - updateDialog.closeMargin / 2
+
+ MouseArea {
+ width: parent.width
+ height: parent.height
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ drag {
+ target: root
+ minimumX: 0
+ minimumY: 0
+ maximumX: root.parent ? root.maximumX : 0
+ maximumY: root.parent ? root.maximumY : 0
+ }
+ }
+ }
+
+ Image {
+ id: logo
+ source: "../images/interface-logo.svg"
+ width: updateDialog.logoSize
+ height: updateDialog.logoSize
+ anchors {
+ top: mainContent.top
+ right: mainContent.right
+ }
+ }
+
Column {
id: mainContent
- width: updateDialog.inputWidth
+ width: updateDialog.contentWidth
spacing: updateDialog.inputSpacing
anchors {
horizontalCenter: parent.horizontalCenter
- verticalCenter: parent.verticalCenter
+ topMargin: updateDialog.borderWidth
+ top: parent.top
}
Rectangle {
- id: backgroundRectangle
- color: "#2c86b1"
- opacity: 0.85
- radius: updateDialog.closeMargin * 2
-
- width: updateDialog.inputWidth + updateDialog.borderWidth * 2
- height: updateDialog.inputHeight * 6 + updateDialog.closeMargin * 2
-
- Rectangle {
- id: dialogTitle
- width: updateDialog.inputWidth
- height: updateDialog.inputHeight
- radius: height / 2
- color: "#ebebeb"
-
+ id: header
+ width: parent.width - updateDialog.logoSize - updateDialog.inputSpacing
+ height: updateAvailable.height + versionDetails.height
+
+ Text {
+ id: updateAvailable
+ text: "Update Available"
+ font {
+ family: updateDialog.fontFamily
+ pixelSize: hifi.fonts.pixelSize * 1.5
+ weight: Font.DemiBold
+ }
+ color: "#303030"
+ }
+
+ Text {
+ id: versionDetails
+ text: updateDialog.updateAvailableDetails
+ font {
+ family: updateDialog.fontFamily
+ pixelSize: hifi.fonts.pixelSize * 0.6
+ letterSpacing: -0.5
+ }
+ color: hifi.colors.text
anchors {
- top: parent.top
- topMargin: updateDialog.inputSpacing
- horizontalCenter: parent.horizontalCenter
- }
-
- Text {
- id: updateAvailableText
- text: "Update Available"
- anchors {
- verticalCenter: parent.verticalCenter
- left: parent.left
- leftMargin: updateDialog.inputSpacing
- }
- }
-
- Text {
- text: updateDialog.updateAvailableDetails
- font.pixelSize: 14
- color: hifi.colors.text
- anchors {
- verticalCenter: parent.verticalCenter
- left: updateAvailableText.right
- leftMargin: 13
- }
+ top: updateAvailable.bottom
}
}
-
- Flickable {
+ }
+
+ Rectangle {
+ width: parent.width
+ height: updateDialog.noticeHeight
+
+ border {
+ width: 1
+ color: "#a0a0a0"
+ }
+
+ ScrollView {
id: scrollArea
- anchors {
- top: dialogTitle.bottom
- }
- contentWidth: updateDialog.inputWidth
- contentHeight: backgroundRectangle.height - (dialogTitle.height * 2.5)
- width: updateDialog.inputWidth
- height: backgroundRectangle.height - (dialogTitle.height * 2.5)
- flickableDirection: Flickable.VerticalFlick
- clip: true
-
- TextEdit {
- id: releaseNotes
- wrapMode: TextEdit.Wrap
- width: parent.width
- readOnly: true
- text: updateDialog.releaseNotes
- font.pixelSize: 14
- color: hifi.colors.text
- anchors {
- left: parent.left
- leftMargin: updateDialog.borderWidth
- }
- }
- }
-
- Rectangle {
- id: downloadButton
- width: updateDialog.buttonWidth
- height: updateDialog.buttonHeight
- radius: updateDialog.buttonRadius
- color: "green"
- anchors {
- top: scrollArea.bottom
- topMargin: 10
- right: backgroundRectangle.right
- rightMargin: 15
- }
+ width: parent.width - updateDialog.closeMargin
+ height: parent.height
+ horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
+ verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
+ anchors.right: parent.right
+
Text {
- text: "Upgrade"
- anchors {
- verticalCenter: parent.verticalCenter
- horizontalCenter: parent.horizontalCenter
+ id: releaseNotes
+ wrapMode: Text.Wrap
+ width: parent.width - updateDialog.closeMargin
+ text: updateDialog.releaseNotes
+ color: hifi.colors.text
+ font {
+ family: updateDialog.fontFamily
+ pixelSize: hifi.fonts.pixelSize * 0.65
}
}
- MouseArea {
- id: downloadButtonAction
- anchors.fill: parent
- onClicked: updateDialog.triggerUpgrade()
- cursorShape: "PointingHandCursor"
- }
}
-
+ }
+
+ Row {
+ anchors.right: parent.right
+ spacing: updateDialog.inputSpacing
+ height: updateDialog.buttonHeight + updateDialog.closeMargin / 2
+
Rectangle {
id: cancelButton
width: updateDialog.buttonWidth
height: updateDialog.buttonHeight
- radius: updateDialog.buttonRadius
- color: "red"
- anchors {
- top: scrollArea.bottom
- topMargin: 10
- right: downloadButton.left
- rightMargin: 15
- }
-
+ anchors.bottom: parent.bottom
+
Text {
text: "Cancel"
+ color: "#0c9ab4" // Same as logo
+ font {
+ family: updateDialog.fontFamily
+ pixelSize: hifi.fonts.pixelSize * 1.2
+ weight: Font.DemiBold
+ }
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
}
+
MouseArea {
id: cancelButtonAction
anchors.fill: parent
@@ -166,7 +178,35 @@ DialogContainer {
cursorShape: "PointingHandCursor"
}
}
+
+ Rectangle {
+ id: updateButton
+ width: updateDialog.buttonWidth
+ height: updateDialog.buttonHeight
+ anchors.bottom: parent.bottom
+
+ Text {
+ text: "Update"
+ color: "#0c9ab4" // Same as logo
+ font {
+ family: updateDialog.fontFamily
+ pixelSize: hifi.fonts.pixelSize * 1.2
+ weight: Font.DemiBold
+ }
+ anchors {
+ verticalCenter: parent.verticalCenter
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ MouseArea {
+ id: updateButtonAction
+ anchors.fill: parent
+ onClicked: updateDialog.triggerUpgrade()
+ cursorShape: "PointingHandCursor"
+ }
+ }
}
}
}
-}
\ No newline at end of file
+}
diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp
index ba37112fe1..0c5145b596 100644
--- a/interface/src/avatar/AvatarActionHold.cpp
+++ b/interface/src/avatar/AvatarActionHold.cpp
@@ -70,30 +70,14 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
return;
}
- // check for NaNs
- if (position.x != position.x ||
- position.y != position.y ||
- position.z != position.z) {
- qDebug() << "AvatarActionHold::updateActionWorker -- target position includes NaN";
- return;
- }
- if (rotation.x != rotation.x ||
- rotation.y != rotation.y ||
- rotation.z != rotation.z ||
- rotation.w != rotation.w) {
- qDebug() << "AvatarActionHold::updateActionWorker -- target rotation includes NaN";
- return;
- }
-
if (_positionalTarget != position || _rotationalTarget != rotation) {
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
}
+ _positionalTarget = position;
+ _rotationalTarget = rotation;
}
-
- _positionalTarget = position;
- _rotationalTarget = rotation;
unlock();
ObjectActionSpring::updateActionWorker(deltaTimeStep);
@@ -101,59 +85,51 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
- bool rPOk = true;
+ bool ok = true;
glm::vec3 relativePosition =
- EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", rPOk, false);
- bool rROk = true;
+ EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
+ if (!ok) {
+ relativePosition = _relativePosition;
+ }
+
+ ok = true;
glm::quat relativeRotation =
- EntityActionInterface::extractQuatArgument("hold", arguments, "relativeRotation", rROk, false);
- bool tSOk = true;
+ EntityActionInterface::extractQuatArgument("hold", arguments, "relativeRotation", ok, false);
+ if (!ok) {
+ relativeRotation = _relativeRotation;
+ }
+
+ ok = true;
float timeScale =
- EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", tSOk, false);
- bool hOk = true;
+ EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", ok, false);
+ if (!ok) {
+ timeScale = _linearTimeScale;
+ }
+
+ ok = true;
QString hand =
- EntityActionInterface::extractStringArgument("hold", arguments, "hand", hOk, false);
+ EntityActionInterface::extractStringArgument("hold", arguments, "hand", ok, false);
+ if (!ok || !(hand == "left" || hand == "right")) {
+ hand = _hand;
+ }
- lockForWrite();
- if (rPOk) {
+ if (relativePosition != _relativePosition
+ || relativeRotation != _relativeRotation
+ || timeScale != _linearTimeScale
+ || hand != _hand) {
+ lockForWrite();
_relativePosition = relativePosition;
- } else {
- _relativePosition = glm::vec3(0.0f, 0.0f, 1.0f);
- }
-
- if (rROk) {
_relativeRotation = relativeRotation;
- } else {
- _relativeRotation = glm::quat(0.0f, 0.0f, 0.0f, 1.0f);
- }
+ const float MIN_TIMESCALE = 0.1f;
+ _linearTimeScale = glm::min(MIN_TIMESCALE, timeScale);
+ _angularTimeScale = _linearTimeScale;
+ _hand = hand;
- if (tSOk) {
- _linearTimeScale = timeScale;
- _angularTimeScale = timeScale;
- } else {
- _linearTimeScale = 0.2f;
- _angularTimeScale = 0.2f;
+ _mine = true;
+ _active = true;
+ activateBody();
+ unlock();
}
-
- if (hOk) {
- hand = hand.toLower();
- if (hand == "left") {
- _hand = "left";
- } else if (hand == "right") {
- _hand = "right";
- } else {
- qDebug() << "hold action -- invalid hand argument:" << hand;
- _hand = "right";
- }
- } else {
- _hand = "right";
- }
-
- _mine = true;
- _positionalTargetSet = true;
- _rotationalTargetSet = true;
- _active = true;
- unlock();
return true;
}
diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp
index 69dfc343d9..6fa2d858fb 100644
--- a/interface/src/ui/UpdateDialog.cpp
+++ b/interface/src/ui/UpdateDialog.cpp
@@ -24,9 +24,18 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) :
int currentVersion = QCoreApplication::applicationVersion().toInt();
int latestVersion = applicationUpdater.data()->getBuildData().lastKey();
int versionsBehind = latestVersion - currentVersion;
- _updateAvailableDetails = "v" + QString::number(latestVersion) + " released on " + applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"];
- _updateAvailableDetails += "\nYou are " + QString::number(versionsBehind) + " versions behind";
- _releaseNotes = applicationUpdater.data()->getBuildData()[latestVersion]["releaseNotes"];
+ _updateAvailableDetails = "v" + QString::number(latestVersion) + " released on "
+ + QString(applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"]).replace(" ", " ");
+ _updateAvailableDetails += "\nYou are " + QString::number(versionsBehind) + " version"
+ + (versionsBehind > 1 ? "s" : "") + " behind";
+
+ _releaseNotes = "";
+ for (int i = latestVersion; i > currentVersion; i--) {
+ QString releaseNotes = applicationUpdater.data()->getBuildData()[i]["releaseNotes"];
+ releaseNotes.remove("
");
+ releaseNotes.remove(QRegExp("^\n+"));
+ _releaseNotes += "\n" + QString().sprintf("%d", i) + "\n" + releaseNotes + "\n";
+ }
}
const QString& UpdateDialog::updateAvailableDetails() const {
diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp
index ddab8040b1..dfe5a3ad74 100644
--- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp
+++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp
@@ -35,14 +35,13 @@ void Rectangle3DOverlay::render(RenderArgs* args) {
if (!_visible) {
return; // do nothing if we're not visible
}
-
+
float alpha = getAlpha();
xColor color = getColor();
const float MAX_COLOR = 255.0f;
glm::vec4 rectangleColor(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
glm::vec3 position = getPosition();
- glm::vec3 center = getCenter();
glm::vec2 dimensions = getDimensions();
glm::vec2 halfDimensions = dimensions * 0.5f;
glm::quat rotation = getRotation();
@@ -67,7 +66,7 @@ void Rectangle3DOverlay::render(RenderArgs* args) {
glm::vec3 point2(halfDimensions.x, -halfDimensions.y, 0.0f);
glm::vec3 point3(halfDimensions.x, halfDimensions.y, 0.0f);
glm::vec3 point4(-halfDimensions.x, halfDimensions.y, 0.0f);
-
+
geometryCache->renderDashedLine(*batch, point1, point2, rectangleColor);
geometryCache->renderDashedLine(*batch, point2, point3, rectangleColor);
geometryCache->renderDashedLine(*batch, point3, point4, rectangleColor);
diff --git a/libraries/animation/src/AnimationLoop.cpp b/libraries/animation/src/AnimationLoop.cpp
index e60df1eaf9..43e049f851 100644
--- a/libraries/animation/src/AnimationLoop.cpp
+++ b/libraries/animation/src/AnimationLoop.cpp
@@ -84,14 +84,14 @@ void AnimationLoop::setStartAutomatically(bool startAutomatically) {
}
void AnimationLoop::setRunning(bool running) {
- if (_running == running) {
+ // don't do anything if the new value is the same as the value we already have
+ if (_running != running) {
+ _running = running;
+
+ // If we just set running to true, then also reset the frame to the first frame
if (running) {
// move back to the beginning
_frameIndex = _firstFrame;
}
- return;
- }
- if ((_running = running)) {
- _frameIndex = _firstFrame;
}
}
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
index 11d24c6d9d..6be891e147 100644
--- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp
+++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
@@ -93,16 +93,18 @@ void EntityTreeRenderer::clear() {
foreach (const EntityItemID& entityID, _entityScripts.keys()) {
checkAndCallUnload(entityID);
}
- OctreeRenderer::clear();
_entityScripts.clear();
auto scene = _viewState->getMain3DScene();
render::PendingChanges pendingChanges;
+
foreach(auto entity, _entitiesInScene) {
entity->removeFromScene(entity, scene, pendingChanges);
}
scene->enqueuePendingChanges(pendingChanges);
_entitiesInScene.clear();
+
+ OctreeRenderer::clear();
}
void EntityTreeRenderer::init() {
@@ -1003,7 +1005,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
checkAndCallUnload(entityID);
}
_entityScripts.remove(entityID);
-
+
// here's where we remove the entity payload from the scene
if (_entitiesInScene.contains(entityID)) {
auto entity = _entitiesInScene.take(entityID);
@@ -1164,6 +1166,7 @@ void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
} else {
_entityIDsLastInScene = _entitiesInScene.keys();
for (auto entityID : _entityIDsLastInScene) {
+ // FIXME - is this really right? do we want to do the deletingEntity() code or just remove from the scene.
deletingEntity(entityID);
}
}
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 85b7bafc78..2deb34d1f8 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -189,6 +189,7 @@ void makeEntityItemStatusGetters(RenderableModelEntityItem* entity, render::Item
bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_ptr scene,
render::PendingChanges& pendingChanges) {
+
_myMetaItem = scene->allocateID();
auto renderData = RenderableModelEntityItemMeta::Pointer(new RenderableModelEntityItemMeta(self));
@@ -199,7 +200,10 @@ bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_p
if (_model) {
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(this, statusGetters);
- return _model->addToScene(scene, pendingChanges, statusGetters);
+
+ // note: we don't care if the model fails to add items, we always added our meta item and therefore we return
+ // true so that the system knows our meta item is in the scene!
+ _model->addToScene(scene, pendingChanges, statusGetters);
}
return true;
diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp
index 2b723d4e15..ba7f3afea4 100644
--- a/libraries/entities/src/EntityActionInterface.cpp
+++ b/libraries/entities/src/EntityActionInterface.cpp
@@ -127,21 +127,21 @@ glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVarian
qDebug() << objectName << "requires argument:" << argumentName;
}
ok = false;
- return glm::vec3();
+ return glm::vec3(0.0f);
}
QVariant resultV = arguments[argumentName];
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
qDebug() << objectName << "argument" << argumentName << "must be a map";
ok = false;
- return glm::vec3();
+ return glm::vec3(0.0f);
}
QVariantMap resultVM = resultV.toMap();
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) {
- qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z";
+ qDebug() << objectName << "argument" << argumentName << "must be a map with keys: x, y, z";
ok = false;
- return glm::vec3();
+ return glm::vec3(0.0f);
}
QVariant xV = resultVM["x"];
@@ -155,9 +155,15 @@ glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVarian
float y = yV.toFloat(&yOk);
float z = zV.toFloat(&zOk);
if (!xOk || !yOk || !zOk) {
- qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z and values of type float.";
+ qDebug() << objectName << "argument" << argumentName << "must be a map with keys: x, y, and z of type float.";
ok = false;
- return glm::vec3();
+ return glm::vec3(0.0f);
+ }
+
+ if (x != x || y != y || z != z) {
+ // at least one of the values is NaN
+ ok = false;
+ return glm::vec3(0.0f);
}
return glm::vec3(x, y, z);
@@ -181,8 +187,8 @@ glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVarian
}
QVariantMap resultVM = resultV.toMap();
- if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) {
- qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z";
+ if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z") || !resultVM.contains("w")) {
+ qDebug() << objectName << "argument" << argumentName << "must be a map with keys: x, y, z, and w";
ok = false;
return glm::quat();
}
@@ -202,12 +208,18 @@ glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVarian
float w = wV.toFloat(&wOk);
if (!xOk || !yOk || !zOk || !wOk) {
qDebug() << objectName << "argument" << argumentName
- << "must be a map with keys of x, y, z, w and values of type float.";
+ << "must be a map with keys: x, y, z, and w of type float.";
ok = false;
return glm::quat();
}
- return glm::quat(w, x, y, z);
+ if (x != x || y != y || z != z || w != w) {
+ // at least one of the components is NaN!
+ ok = false;
+ return glm::quat();
+ }
+
+ return glm::normalize(glm::quat(w, x, y, z));
}
float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMap arguments,
@@ -224,7 +236,7 @@ float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMa
bool vOk = true;
float v = vV.toFloat(&vOk);
- if (!vOk) {
+ if (!vOk || v != v) {
ok = false;
return 0.0f;
}
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 794d0752a1..7354628390 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -385,7 +385,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
dataAt += encodedID.size();
bytesRead += encodedID.size();
Q_ASSERT(id == _id);
- Q_ASSERT(parser.offset() == bytesRead);
+ Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
#endif
@@ -400,8 +400,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
quint32 type = typeCoder;
EntityTypes::EntityType oldType = (EntityTypes::EntityType)type;
Q_ASSERT(oldType == _type);
- Q_ASSERT(parser.offset() == bytesRead);
-#endif
+ Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
+#endif
bool overwriteLocalData = true; // assume the new content overwrites our local data
quint64 now = usecTimestampNow();
@@ -417,9 +417,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
dataAt += sizeof(createdFromBuffer2);
bytesRead += sizeof(createdFromBuffer2);
Q_ASSERT(createdFromBuffer2 == createdFromBuffer);
- Q_ASSERT(parser.offset() == bytesRead);
+ Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
-#endif
+#endif
if (_created == UNKNOWN_CREATED_TIME) {
// we don't yet have a _created timestamp, so we accept this one
createdFromBuffer -= clockSkew;
@@ -458,9 +458,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
dataAt += sizeof(lastEditedFromBuffer2);
bytesRead += sizeof(lastEditedFromBuffer2);
Q_ASSERT(lastEditedFromBuffer2 == lastEditedFromBuffer);
- Q_ASSERT(parser.offset() == bytesRead);
+ Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
-#endif
+#endif
quint64 lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew;
if (lastEditedFromBufferAdjusted > now) {
lastEditedFromBufferAdjusted = now;
@@ -534,10 +534,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
encodedUpdateDelta = updateDeltaCoder; // determine true length
dataAt += encodedUpdateDelta.size();
bytesRead += encodedUpdateDelta.size();
- Q_ASSERT(parser.offset() == bytesRead);
+ Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
-#endif
-
+#endif
+
if (overwriteLocalData) {
_lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that
#ifdef WANT_DEBUG
@@ -562,7 +562,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
encodedSimulatedDelta = simulatedDeltaCoder; // determine true length
dataAt += encodedSimulatedDelta.size();
bytesRead += encodedSimulatedDelta.size();
- Q_ASSERT(parser.offset() == bytesRead);
+ Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
#endif
@@ -599,7 +599,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
dataAt += propertyFlags.getEncodedLength();
bytesRead += propertyFlags.getEncodedLength();
Q_ASSERT(propertyFlags2 == propertyFlags);
- Q_ASSERT(parser.offset() == bytesRead);
+ Q_ASSERT(parser.offset() == (unsigned int)bytesRead);
}
#endif
@@ -1496,7 +1496,7 @@ bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer act
bool result = addActionInternal(simulation, action);
if (!result) {
- removeAction(simulation, action->getID());
+ removeActionInternal(action->getID());
}
unlock();
@@ -1520,6 +1520,7 @@ bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPoi
QByteArray newDataCache = serializeActions(success);
if (success) {
_allActionsDataCache = newDataCache;
+ _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
}
return success;
}
@@ -1537,6 +1538,7 @@ bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionI
bool success = action->updateArguments(arguments);
if (success) {
_allActionsDataCache = serializeActions(success);
+ _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
} else {
qDebug() << "EntityItem::updateAction failed";
}
@@ -1572,6 +1574,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* s
bool success = true;
_allActionsDataCache = serializeActions(success);
+ _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
return success;
}
return false;
@@ -1590,6 +1593,7 @@ bool EntityItem::clearActions(EntitySimulation* simulation) {
// empty _serializedActions means no actions for the EntityItem
_actionsToRemove.clear();
_allActionsDataCache.clear();
+ _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
unlock();
return true;
}
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 9a1a5494b7..106e0a6b8b 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -128,6 +128,10 @@ void EntityItemProperties::setSittingPoints(const QVector& sitting
}
}
+bool EntityItemProperties::animationSettingsChanged() const {
+ return _animationSettingsChanged;
+}
+
void EntityItemProperties::setAnimationSettings(const QString& value) {
// the animations setting is a JSON string that may contain various animation settings.
// if it includes fps, frameIndex, or running, those values will be parsed out and
diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index f1c6157694..18175da1f6 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -543,6 +543,9 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
}
bool success = actor(simulation, entity);
+ if (success) {
+ _entityTree->entityChanged(entity);
+ }
_entityTree->unlock();
// transmit the change
diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp
index 1168b119a3..b79f506544 100644
--- a/libraries/gpu/src/gpu/GLBackend.cpp
+++ b/libraries/gpu/src/gpu/GLBackend.cpp
@@ -204,7 +204,6 @@ void GLBackend::do_drawInstanced(Batch& batch, uint32 paramOffset) {
GLenum mode = _primitiveToGLmode[primitiveType];
uint32 numVertices = batch._params[paramOffset + 2]._uint;
uint32 startVertex = batch._params[paramOffset + 1]._uint;
- uint32 startInstance = batch._params[paramOffset + 0]._uint;
glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances);
(void) CHECK_GL_ERROR();
@@ -238,7 +237,7 @@ void GLBackend::do_clearFramebuffer(Batch& batch, uint32 paramOffset) {
std::vector drawBuffers;
if (masks & Framebuffer::BUFFER_COLORS) {
- for (int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) {
+ for (unsigned int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) {
if (masks & (1 << i)) {
drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i);
}
diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp
index 101b69f03a..5205e08c62 100644
--- a/libraries/physics/src/ObjectAction.cpp
+++ b/libraries/physics/src/ObjectAction.cpp
@@ -129,3 +129,10 @@ void ObjectAction::setAngularVelocity(glm::vec3 angularVelocity) {
rigidBody->activate();
}
+void ObjectAction::activateBody() {
+ auto rigidBody = getRigidBody();
+ if (rigidBody) {
+ rigidBody->activate();
+ }
+}
+
diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h
index f619657e92..f27ed9ab07 100644
--- a/libraries/physics/src/ObjectAction.h
+++ b/libraries/physics/src/ObjectAction.h
@@ -55,6 +55,7 @@ protected:
virtual void setLinearVelocity(glm::vec3 linearVelocity);
virtual glm::vec3 getAngularVelocity();
virtual void setAngularVelocity(glm::vec3 angularVelocity);
+ virtual void activateBody();
void lockForRead() { _lock.lockForRead(); }
bool tryLockForRead() { return _lock.tryLockForRead(); }
diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp
index 2391ded13d..a00bbbd418 100644
--- a/libraries/physics/src/ObjectActionOffset.cpp
+++ b/libraries/physics/src/ObjectActionOffset.cpp
@@ -59,10 +59,6 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
const float MAX_LINEAR_TIMESCALE = 600.0f; // 10 minutes is a long time
if (_positionalTargetSet && _linearTimeScale < MAX_LINEAR_TIMESCALE) {
- if (_needsActivation) {
- rigidBody->activate();
- _needsActivation = false;
- }
glm::vec3 objectPosition = bulletToGLM(rigidBody->getCenterOfMassPosition());
glm::vec3 springAxis = objectPosition - _pointToOffsetFrom; // from anchor to object
float distance = glm::length(springAxis);
@@ -95,26 +91,21 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
glm::vec3 pointToOffsetFrom =
EntityActionInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true);
if (!ok) {
- return false;
+ pointToOffsetFrom = _pointToOffsetFrom;
}
ok = true;
float linearTimeScale =
EntityActionInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false);
- if (ok) {
- if (linearTimeScale <= 0.0f) {
- qDebug() << "offset action -- linearTimeScale must be greater than zero.";
- return false;
- }
- } else {
- linearTimeScale = 0.1f;
+ if (!ok) {
+ linearTimeScale = _linearTimeScale;
}
ok = true;
float linearDistance =
EntityActionInterface::extractFloatArgument("offset action", arguments, "linearDistance", ok, false);
if (!ok) {
- linearDistance = 0.0f;
+ linearDistance = _linearDistance;
}
// only change stuff if something actually changed
@@ -127,7 +118,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
_linearDistance = linearDistance;
_positionalTargetSet = true;
_active = true;
- _needsActivation = true;
+ activateBody();
unlock();
}
return true;
diff --git a/libraries/physics/src/ObjectActionOffset.h b/libraries/physics/src/ObjectActionOffset.h
index a0190f3832..1918da6996 100644
--- a/libraries/physics/src/ObjectActionOffset.h
+++ b/libraries/physics/src/ObjectActionOffset.h
@@ -36,7 +36,6 @@ public:
float _linearDistance;
float _linearTimeScale;
bool _positionalTargetSet;
- bool _needsActivation = true;
};
#endif // hifi_ObjectActionOffset_h
diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp
index cb1dd20472..196cc8d3ea 100644
--- a/libraries/physics/src/ObjectActionSpring.cpp
+++ b/libraries/physics/src/ObjectActionSpring.cpp
@@ -17,14 +17,15 @@ const float SPRING_MAX_SPEED = 10.0f;
const uint16_t ObjectActionSpring::springVersion = 1;
+
ObjectActionSpring::ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectAction(ACTION_TYPE_SPRING, id, ownerEntity),
_positionalTarget(glm::vec3(0.0f)),
- _linearTimeScale(0.2f),
- _positionalTargetSet(false),
+ _linearTimeScale(FLT_MAX),
+ _positionalTargetSet(true),
_rotationalTarget(glm::quat()),
- _angularTimeScale(0.2f),
- _rotationalTargetSet(false) {
+ _angularTimeScale(FLT_MAX),
+ _rotationalTargetSet(true) {
#if WANT_DEBUG
qDebug() << "ObjectActionSpring::ObjectActionSpring";
#endif
@@ -61,130 +62,92 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
return;
}
- // handle the linear part
- if (_positionalTargetSet) {
- // check for NaN
- if (_positionalTarget.x != _positionalTarget.x ||
- _positionalTarget.y != _positionalTarget.y ||
- _positionalTarget.z != _positionalTarget.z) {
- qDebug() << "ObjectActionSpring::updateActionWorker -- target position includes NaN";
- unlock();
- lockForWrite();
- _active = false;
- unlock();
- return;
- }
- glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition());
- float offsetLength = glm::length(offset);
- float speed = offsetLength / _linearTimeScale;
+ const float MAX_TIMESCALE = 600.0f; // 10 min is a long time
+ if (_linearTimeScale < MAX_TIMESCALE) {
+ btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget);
+ float offsetLength = offset.length();
+ float speed = (offsetLength > FLT_EPSILON) ? glm::min(offsetLength / _linearTimeScale, SPRING_MAX_SPEED) : 0.0f;
- // cap speed
- if (speed > SPRING_MAX_SPEED) {
- speed = SPRING_MAX_SPEED;
- }
-
- if (offsetLength > IGNORE_POSITION_DELTA) {
- glm::vec3 newVelocity = glm::normalize(offset) * speed;
- rigidBody->setLinearVelocity(glmToBullet(newVelocity));
- rigidBody->activate();
- } else {
- rigidBody->setLinearVelocity(glmToBullet(glm::vec3(0.0f)));
- }
+ // this action is aggresively critically damped and defeats the current velocity
+ rigidBody->setLinearVelocity((- speed / offsetLength) * offset);
}
- // handle rotation
- if (_rotationalTargetSet) {
- if (_rotationalTarget.x != _rotationalTarget.x ||
- _rotationalTarget.y != _rotationalTarget.y ||
- _rotationalTarget.z != _rotationalTarget.z ||
- _rotationalTarget.w != _rotationalTarget.w) {
- qDebug() << "AvatarActionHold::updateActionWorker -- target rotation includes NaN";
- unlock();
- lockForWrite();
- _active = false;
- unlock();
- return;
- }
+ if (_angularTimeScale < MAX_TIMESCALE) {
+ btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
- glm::quat bodyRotation = bulletToGLM(rigidBody->getOrientation());
- // if qZero and qOne are too close to each other, we can get NaN for angle.
- auto alignmentDot = glm::dot(bodyRotation, _rotationalTarget);
- const float almostOne = 0.99999f;
- if (glm::abs(alignmentDot) < almostOne) {
- glm::quat target = _rotationalTarget;
- if (alignmentDot < 0) {
+ btQuaternion bodyRotation = rigidBody->getOrientation();
+ auto alignmentDot = bodyRotation.dot(glmToBullet(_rotationalTarget));
+ const float ALMOST_ONE = 0.99999f;
+ if (glm::abs(alignmentDot) < ALMOST_ONE) {
+ btQuaternion target = glmToBullet(_rotationalTarget);
+ if (alignmentDot < 0.0f) {
target = -target;
}
- glm::quat qZeroInverse = glm::inverse(bodyRotation);
- glm::quat deltaQ = target * qZeroInverse;
- glm::vec3 axis = glm::axis(deltaQ);
- float angle = glm::angle(deltaQ);
- assert(!isNaN(angle));
- glm::vec3 newAngularVelocity = (angle / _angularTimeScale) * glm::normalize(axis);
- rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity));
- rigidBody->activate();
- } else {
- rigidBody->setAngularVelocity(glmToBullet(glm::vec3(0.0f)));
+ // if dQ is the incremental rotation that gets an object from Q0 to Q1 then:
+ //
+ // Q1 = dQ * Q0
+ //
+ // solving for dQ gives:
+ //
+ // dQ = Q1 * Q0^
+ btQuaternion deltaQ = target * bodyRotation.inverse();
+ float angle = deltaQ.getAngle();
+ const float MIN_ANGLE = 1.0e-4;
+ if (angle > MIN_ANGLE) {
+ targetVelocity = (angle / _angularTimeScale) * deltaQ.getAxis();
+ }
}
+ // this action is aggresively critically damped and defeats the current velocity
+ rigidBody->setAngularVelocity(targetVelocity);
}
-
unlock();
}
+const float MIN_TIMESCALE = 0.1f;
bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
// targets are required, spring-constants are optional
- bool ptOk = true;
+ bool ok = true;
glm::vec3 positionalTarget =
- EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ptOk, false);
- bool pscOk = true;
+ EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false);
+ if (!ok) {
+ positionalTarget = _positionalTarget;
+ }
+ ok = true;
float linearTimeScale =
- EntityActionInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", pscOk, false);
- if (ptOk && pscOk && linearTimeScale <= 0.0f) {
- qDebug() << "spring action -- linearTimeScale must be greater than zero.";
- return false;
+ EntityActionInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false);
+ if (!ok || linearTimeScale <= 0.0f) {
+ linearTimeScale = _linearTimeScale;
}
- bool rtOk = true;
+ ok = true;
glm::quat rotationalTarget =
- EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", rtOk, false);
- bool rscOk = true;
+ EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false);
+ if (!ok) {
+ rotationalTarget = _rotationalTarget;
+ }
+
+ ok = true;
float angularTimeScale =
- EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", rscOk, false);
-
- if (!ptOk && !rtOk) {
- qDebug() << "spring action requires at least one of targetPosition or targetRotation argument";
- return false;
+ EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", ok, false);
+ if (!ok) {
+ angularTimeScale = _angularTimeScale;
}
- lockForWrite();
-
- _positionalTargetSet = _rotationalTargetSet = false;
-
- if (ptOk) {
+ if (positionalTarget != _positionalTarget
+ || linearTimeScale != _linearTimeScale
+ || rotationalTarget != _rotationalTarget
+ || angularTimeScale != _angularTimeScale) {
+ // something changed
+ lockForWrite();
_positionalTarget = positionalTarget;
- _positionalTargetSet = true;
-
- if (pscOk) {
- _linearTimeScale = linearTimeScale;
- } else {
- _linearTimeScale = 0.1f;
- }
- }
-
- if (rtOk) {
+ _linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
_rotationalTarget = rotationalTarget;
- _rotationalTargetSet = true;
-
- if (rscOk) {
- _angularTimeScale = angularTimeScale;
- } else {
- _angularTimeScale = 0.1f;
- }
+ _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
+ _active = true;
+ activateBody();
+ unlock();
}
-
- _active = true;
- unlock();
return true;
}
@@ -192,15 +155,11 @@ QVariantMap ObjectActionSpring::getArguments() {
QVariantMap arguments;
lockForRead();
- if (_positionalTargetSet) {
- arguments["linearTimeScale"] = _linearTimeScale;
- arguments["targetPosition"] = glmToQMap(_positionalTarget);
- }
+ arguments["linearTimeScale"] = _linearTimeScale;
+ arguments["targetPosition"] = glmToQMap(_positionalTarget);
- if (_rotationalTargetSet) {
- arguments["targetRotation"] = glmToQMap(_rotationalTarget);
- arguments["angularTimeScale"] = _angularTimeScale;
- }
+ arguments["targetRotation"] = glmToQMap(_rotationalTarget);
+ arguments["angularTimeScale"] = _angularTimeScale;
unlock();
return arguments;
@@ -210,7 +169,7 @@ QByteArray ObjectActionSpring::serialize() const {
QByteArray serializedActionArguments;
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
- dataStream << getType();
+ dataStream << ACTION_TYPE_SPRING;
dataStream << getID();
dataStream << ObjectActionSpring::springVersion;
diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp
index 4c321f8fd9..b2db089bbe 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.cpp
+++ b/libraries/render-utils/src/DeferredLightingEffect.cpp
@@ -474,17 +474,18 @@ void DeferredLightingEffect::render(RenderArgs* args) {
// IN DEBUG: light->setShowContour(true);
batch.setUniformBuffer(_spotLightLocations.lightBufferUnit, light->getSchemaBuffer());
-
+
auto eyeLightPos = eyePoint - light->getPosition();
auto eyeHalfPlaneDistance = glm::dot(eyeLightPos, light->getDirection());
-
+
const float TANGENT_LENGTH_SCALE = 0.666f;
- glm::vec4 coneParam(light->getSpotAngleCosSin(), TANGENT_LENGTH_SCALE * tan(0.5 * light->getSpotAngle()), 1.0f);
+ glm::vec4 coneParam(light->getSpotAngleCosSin(), TANGENT_LENGTH_SCALE * tanf(0.5f * light->getSpotAngle()), 1.0f);
float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION);
// TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume,
// we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working...
- if ((eyeHalfPlaneDistance > -nearRadius) && (glm::distance(eyePoint, glm::vec3(light->getPosition())) < expandedRadius + nearRadius)) {
+ if ((eyeHalfPlaneDistance > -nearRadius) &&
+ (glm::distance(eyePoint, glm::vec3(light->getPosition())) < expandedRadius + nearRadius)) {
coneParam.w = 0.0f;
batch._glUniform4fv(_spotLightLocations.coneParam, 1, reinterpret_cast< const GLfloat* >(&coneParam));
diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp
index 268f2b6841..1b52145a1c 100644
--- a/libraries/render/src/render/Scene.cpp
+++ b/libraries/render/src/render/Scene.cpp
@@ -76,7 +76,7 @@ void Item::Status::Value::setColor(float hue) {
}
void Item::Status::getPackedValues(glm::ivec4& values) const {
- for (unsigned int i = 0; i < values.length(); i++) {
+ for (unsigned int i = 0; i < (unsigned int)values.length(); i++) {
if (i < _values.size()) {
values[i] = _values[i]().getPackedData();
} else {
diff --git a/libraries/shared/src/BufferParser.h b/libraries/shared/src/BufferParser.h
index 84bde2be31..d60e7127cd 100644
--- a/libraries/shared/src/BufferParser.h
+++ b/libraries/shared/src/BufferParser.h
@@ -44,10 +44,7 @@ public:
template
inline void readFlags(PropertyFlags& result) {
- // FIXME doing heap allocation
- QByteArray encoded((const char*)(_data + _offset), remaining());
- result.decode(encoded);
- _offset += result.getEncodedLength();
+ _offset += result.decode(_data + _offset, remaining());
}
template
diff --git a/libraries/shared/src/PropertyFlags.h b/libraries/shared/src/PropertyFlags.h
index de05edc076..0be7b3af93 100644
--- a/libraries/shared/src/PropertyFlags.h
+++ b/libraries/shared/src/PropertyFlags.h
@@ -25,6 +25,7 @@
#include
#include
+#include "ByteCountCoding.h"
#include
templateclass PropertyFlags {
@@ -51,7 +52,8 @@ public:
void setHasProperty(Enum flag, bool value = true);
bool getHasProperty(Enum flag) const;
QByteArray encode();
- void decode(const QByteArray& fromEncoded);
+ size_t decode(const uint8_t* data, size_t length);
+ size_t decode(const QByteArray& fromEncoded);
operator QByteArray() { return encode(); };
@@ -193,51 +195,62 @@ template inline QByteArray PropertyFlags::encode() {
return output;
}
-template inline void PropertyFlags::decode(const QByteArray& fromEncodedBytes) {
-
+template
+inline size_t PropertyFlags::decode(const uint8_t* data, size_t size) {
clear(); // we are cleared out!
- // first convert the ByteArray into a BitArray...
- QBitArray encodedBits;
- int bitCount = BITS_PER_BYTE * fromEncodedBytes.count();
- encodedBits.resize(bitCount);
-
- for(int byte = 0; byte < fromEncodedBytes.count(); byte++) {
- char originalByte = fromEncodedBytes.at(byte);
- for(int bit = 0; bit < BITS_PER_BYTE; bit++) {
- int shiftBy = BITS_PER_BYTE - (bit + 1);
- char maskBit = ( 1 << shiftBy);
- bool bitValue = originalByte & maskBit;
- encodedBits.setBit(byte * BITS_PER_BYTE + bit, bitValue);
+ size_t bytesConsumed = 0;
+ int bitCount = BITS_IN_BYTE * size;
+
+ int encodedByteCount = 1; // there is at least 1 byte (after the leadBits)
+ int leadBits = 1; // there is always at least 1 lead bit
+ bool inLeadBits = true;
+ int bitAt = 0;
+ int expectedBitCount; // unknown at this point
+ int lastValueBit;
+ for (unsigned int byte = 0; byte < size; byte++) {
+ char originalByte = data[byte];
+ bytesConsumed++;
+ unsigned char maskBit = 0x80; // LEFT MOST BIT set
+ for (int bit = 0; bit < BITS_IN_BYTE; bit++) {
+ bool bitIsSet = originalByte & maskBit;
+ // Processing of the lead bits
+ if (inLeadBits) {
+ if (bitIsSet) {
+ encodedByteCount++;
+ leadBits++;
+ } else {
+ inLeadBits = false; // once we hit our first 0, we know we're out of the lead bits
+ expectedBitCount = (encodedByteCount * BITS_IN_BYTE) - leadBits;
+ lastValueBit = expectedBitCount + bitAt;
+
+ // check to see if the remainder of our buffer is sufficient
+ if (expectedBitCount > (bitCount - leadBits)) {
+ break;
+ }
+ }
+ } else {
+ if (bitAt > lastValueBit) {
+ break;
+ }
+
+ if (bitIsSet) {
+ setHasProperty(static_cast(bitAt - leadBits), true);
+ }
+ }
+ bitAt++;
+ maskBit >>= 1;
}
- }
-
- // next, read the leading bits to determine the correct number of bytes to decode (may not match the QByteArray)
- int encodedByteCount = 0;
- int leadBits = 1;
- int bitAt;
- for (bitAt = 0; bitAt < bitCount; bitAt++) {
- if (encodedBits.at(bitAt)) {
- encodedByteCount++;
- leadBits++;
- } else {
+ if (!inLeadBits && bitAt > lastValueBit) {
break;
}
}
- encodedByteCount++; // always at least one byte
- _encodedLength = encodedByteCount;
+ _encodedLength = bytesConsumed;
+ return bytesConsumed;
+}
- int expectedBitCount = encodedByteCount * BITS_PER_BYTE;
-
- // Now, keep reading...
- if (expectedBitCount <= (encodedBits.size() - leadBits)) {
- int flagsStartAt = bitAt + 1;
- for (bitAt = flagsStartAt; bitAt < expectedBitCount; bitAt++) {
- if (encodedBits.at(bitAt)) {
- setHasProperty((Enum)(bitAt - flagsStartAt));
- }
- }
- }
+template inline size_t PropertyFlags::decode(const QByteArray& fromEncodedBytes) {
+ return decode(reinterpret_cast(fromEncodedBytes.data()), fromEncodedBytes.size());
}
template inline void PropertyFlags::debugDumpBits() {
diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt
new file mode 100644
index 0000000000..0077549100
--- /dev/null
+++ b/tests/entities/CMakeLists.txt
@@ -0,0 +1,12 @@
+
+set(TARGET_NAME "entities-test")
+
+# This is not a testcase -- just set it up as a regular hifi project
+setup_hifi_project(Network Script)
+
+set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
+
+# link in the shared libraries
+link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation environment)
+
+copy_dlls_beside_windows_executable()
\ No newline at end of file
diff --git a/tests/entities/packet.bin b/tests/entities/packet.bin
new file mode 100644
index 0000000000..295117172d
Binary files /dev/null and b/tests/entities/packet.bin differ
diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp
new file mode 100644
index 0000000000..a255ffa995
--- /dev/null
+++ b/tests/entities/src/main.cpp
@@ -0,0 +1,173 @@
+//
+// main.cpp
+// tests/render-utils/src
+//
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+const QString& getTestResourceDir() {
+ static QString dir;
+ if (dir.isEmpty()) {
+ QDir path(__FILE__);
+ path.cdUp();
+ dir = path.cleanPath(path.absoluteFilePath("../")) + "/";
+ qDebug() << "Qml Test Path: " << dir;
+ }
+ return dir;
+}
+
+class StopWatch {
+public:
+ void start() {
+ Q_ASSERT(_start == 0);
+ _start = usecTimestampNow();
+ }
+
+ void stop() {
+ Q_ASSERT(_start != 0);
+ _last = usecTimestampNow() - _start;
+ _start = 0;
+ _total += _last;
+ _count++;
+ }
+
+ quint64 getLast() {
+ return _last;
+ }
+
+ quint64 getTotal() {
+ return _total;
+ }
+
+ float getAverage() {
+ return (float)_total / (float)_count;
+ }
+
+ void reset() {
+ _last = _start = _total = _count = 0;
+ }
+
+private:
+ size_t _count{ 0 };
+ quint64 _total{ 0 };
+ quint64 _start{ 0 };
+ quint64 _last{ 0 };
+};
+
+template
+void testByteCountCodedStable(const T& value) {
+ ByteCountCoded coder((T)value);
+ auto encoded = coder.encode();
+ #ifndef QT_NO_DEBUG
+ auto originalEncodedSize = encoded.size();
+ #endif
+ for (int i = 0; i < 10; ++i) {
+ encoded.append(qrand());
+ }
+ ByteCountCoded decoder;
+ decoder.decode(encoded);
+ Q_ASSERT(decoder.data == coder.data);
+ #ifndef QT_NO_DEBUG
+ auto consumed = decoder.decode(encoded.data(), encoded.size());
+ #endif
+ Q_ASSERT(consumed == (unsigned int)originalEncodedSize);
+
+}
+
+template
+void testByteCountCoded() {
+ testByteCountCodedStable(0);
+ testByteCountCodedStable(1);
+ testByteCountCodedStable(1 << 16);
+ testByteCountCodedStable(std::numeric_limits::max() >> 16);
+ testByteCountCodedStable(std::numeric_limits::max() >> 8);
+ testByteCountCodedStable(std::numeric_limits::max() >> 1);
+ testByteCountCodedStable(std::numeric_limits::max());
+}
+
+void testPropertyFlags(uint32_t value) {
+ EntityPropertyFlags original;
+ original.clear();
+ auto enumSize = sizeof(EntityPropertyList);
+ for (size_t i = 0; i < enumSize * 8; ++i) {
+ original.setHasProperty((EntityPropertyList)i);
+ }
+ QByteArray encoded = original.encode();
+ #ifndef QT_NO_DEBUG
+ auto originalSize = encoded.size();
+ #endif
+ for (size_t i = 0; i < enumSize; ++i) {
+ encoded.append(qrand());
+ }
+
+ EntityPropertyFlags decodeOld, decodeNew;
+ {
+ decodeOld.decode(encoded);
+ Q_ASSERT(decodeOld == original);
+ }
+
+ {
+ #ifndef QT_NO_DEBUG
+ auto decodeSize = decodeNew.decode((const uint8_t*)encoded.data(), encoded.size());
+ #endif
+ Q_ASSERT(originalSize == decodeSize);
+ Q_ASSERT(decodeNew == original);
+ }
+}
+
+void testPropertyFlags() {
+ testPropertyFlags(0);
+ testPropertyFlags(1);
+ testPropertyFlags(1 << 16);
+ testPropertyFlags(0xFFFF);
+}
+
+int main(int argc, char** argv) {
+ QCoreApplication app(argc, argv);
+ {
+ auto start = usecTimestampNow();
+ for (int i = 0; i < 1000; ++i) {
+ testPropertyFlags();
+ testByteCountCoded();
+ testByteCountCoded();
+ testByteCountCoded();
+ testByteCountCoded();
+ }
+ auto duration = usecTimestampNow() - start;
+ qDebug() << duration;
+
+ }
+ DependencyManager::set(NodeType::Unassigned);
+
+ QFile file(getTestResourceDir() + "packet.bin");
+ if (!file.open(QIODevice::ReadOnly)) return -1;
+ QByteArray packet = file.readAll();
+ EntityItemPointer item = BoxEntityItem::factory(EntityItemID(), EntityItemProperties());
+ ReadBitstreamToTreeParams params;
+ params.bitstreamVersion = 33;
+
+ auto start = usecTimestampNow();
+ for (int i = 0; i < 1000; ++i) {
+ item->readEntityDataFromBuffer(reinterpret_cast(packet.constData()), packet.size(), params);
+ }
+ float duration = (usecTimestampNow() - start);
+ qDebug() << (duration / 1000.0f);
+ return 0;
+}
+
+#include "main.moc"