mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 18:44:00 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into referentials
This commit is contained in:
commit
15cbfa0715
34 changed files with 1416 additions and 194 deletions
|
@ -60,6 +60,413 @@ var jointList = MyAvatar.getJointNames();
|
|||
|
||||
var mode = 0;
|
||||
|
||||
var exportMenu = null;
|
||||
|
||||
var ExportMenu = function(opts) {
|
||||
var self = this;
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
var pos = { x: windowDimensions.x / 2, y: windowDimensions.y - 100 };
|
||||
|
||||
this._onClose = opts.onClose || function() {};
|
||||
this._position = { x: 0.0, y: 0.0, z: 0.0 };
|
||||
this._scale = 1.0;
|
||||
|
||||
var minScale = 1;
|
||||
var maxScale = 32768;
|
||||
var titleWidth = 120;
|
||||
var locationWidth = 100;
|
||||
var scaleWidth = 144;
|
||||
var exportWidth = 100;
|
||||
var cancelWidth = 100;
|
||||
var margin = 4;
|
||||
var height = 30;
|
||||
var outerHeight = height + (2 * margin);
|
||||
var buttonColor = { red: 128, green: 128, blue: 128};
|
||||
|
||||
var SCALE_MINUS = scaleWidth * 40.0 / 100.0;
|
||||
var SCALE_PLUS = scaleWidth * 63.0 / 100.0;
|
||||
|
||||
var fullWidth = locationWidth + scaleWidth + exportWidth + cancelWidth + (2 * margin);
|
||||
var offset = fullWidth / 2;
|
||||
pos.x -= offset;
|
||||
|
||||
var background= Overlays.addOverlay("text", {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
opacity: 1,
|
||||
width: fullWidth,
|
||||
height: outerHeight,
|
||||
backgroundColor: { red: 200, green: 200, blue: 200 },
|
||||
text: "",
|
||||
});
|
||||
|
||||
var titleText = Overlays.addOverlay("text", {
|
||||
x: pos.x,
|
||||
y: pos.y - height,
|
||||
font: { size: 14 },
|
||||
width: titleWidth,
|
||||
height: height,
|
||||
backgroundColor: { red: 255, green: 255, blue: 255 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "Export Models"
|
||||
});
|
||||
|
||||
var locationButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin,
|
||||
y: pos.y + margin,
|
||||
width: locationWidth,
|
||||
height: height,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "0, 0, 0",
|
||||
});
|
||||
var scaleOverlay = Overlays.addOverlay("image", {
|
||||
x: pos.x + margin + locationWidth,
|
||||
y: pos.y + margin,
|
||||
width: scaleWidth,
|
||||
height: height,
|
||||
subImage: { x: 0, y: 3, width: 144, height: height},
|
||||
imageURL: toolIconUrl + "voxel-size-selector.svg",
|
||||
alpha: 0.9,
|
||||
});
|
||||
var scaleViewWidth = 40;
|
||||
var scaleView = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + locationWidth + SCALE_MINUS,
|
||||
y: pos.y + margin,
|
||||
width: scaleViewWidth,
|
||||
height: height,
|
||||
alpha: 0.0,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "1"
|
||||
});
|
||||
var exportButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + locationWidth + scaleWidth,
|
||||
y: pos.y + margin,
|
||||
width: exportWidth,
|
||||
height: height,
|
||||
color: { red: 0, green: 255, blue: 255 },
|
||||
text: "Export"
|
||||
});
|
||||
var cancelButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + locationWidth + scaleWidth + exportWidth,
|
||||
y: pos.y + margin,
|
||||
width: cancelWidth,
|
||||
height: height,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
text: "Cancel"
|
||||
});
|
||||
|
||||
var voxelPreview = Overlays.addOverlay("cube", {
|
||||
position: { x: 0, y: 0, z: 0},
|
||||
size: this._scale,
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
alpha: 1,
|
||||
solid: false,
|
||||
visible: true,
|
||||
lineWidth: 4
|
||||
});
|
||||
|
||||
this.parsePosition = function(str) {
|
||||
var parts = str.split(',');
|
||||
if (parts.length == 3) {
|
||||
var x = parseFloat(parts[0]);
|
||||
var y = parseFloat(parts[1]);
|
||||
var z = parseFloat(parts[2]);
|
||||
if (isFinite(x) && isFinite(y) && isFinite(z)) {
|
||||
return { x: x, y: y, z: z };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.showPositionPrompt = function() {
|
||||
var positionStr = self._position.x + ", " + self._position.y + ", " + self._position.z;
|
||||
while (1) {
|
||||
positionStr = Window.prompt("Position to export form:", positionStr);
|
||||
if (positionStr == null) {
|
||||
break;
|
||||
}
|
||||
var position = self.parsePosition(positionStr);
|
||||
if (position != null) {
|
||||
self.setPosition(position.x, position.y, position.z);
|
||||
break;
|
||||
}
|
||||
Window.alert("The position you entered was invalid.");
|
||||
}
|
||||
};
|
||||
|
||||
this.setScale = function(scale) {
|
||||
self._scale = Math.min(maxScale, Math.max(minScale, scale));
|
||||
Overlays.editOverlay(scaleView, { text: self._scale });
|
||||
Overlays.editOverlay(voxelPreview, { size: self._scale });
|
||||
}
|
||||
|
||||
this.decreaseScale = function() {
|
||||
self.setScale(self._scale /= 2);
|
||||
}
|
||||
|
||||
this.increaseScale = function() {
|
||||
self.setScale(self._scale *= 2);
|
||||
}
|
||||
|
||||
this.exportModels = function() {
|
||||
var x = self._position.x;
|
||||
var y = self._position.y;
|
||||
var z = self._position.z;
|
||||
var s = self._scale;
|
||||
var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo";
|
||||
filename = Window.save("Select where to save", filename, "*.svo")
|
||||
if (filename) {
|
||||
var success = Clipboard.exportModels(filename, x, y, z, s);
|
||||
if (!success) {
|
||||
Window.alert("Export failed: no models found in selected area.");
|
||||
}
|
||||
}
|
||||
self.close();
|
||||
};
|
||||
|
||||
this.getPosition = function() {
|
||||
return self._position;
|
||||
};
|
||||
|
||||
this.setPosition = function(x, y, z) {
|
||||
self._position = { x: x, y: y, z: z };
|
||||
var positionStr = x + ", " + y + ", " + z;
|
||||
Overlays.editOverlay(locationButton, { text: positionStr });
|
||||
Overlays.editOverlay(voxelPreview, { position: self._position });
|
||||
|
||||
};
|
||||
|
||||
this.mouseReleaseEvent = function(event) {
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
||||
if (clickedOverlay == locationButton) {
|
||||
self.showPositionPrompt();
|
||||
} else if (clickedOverlay == exportButton) {
|
||||
self.exportModels();
|
||||
} else if (clickedOverlay == cancelButton) {
|
||||
self.close();
|
||||
} else if (clickedOverlay == scaleOverlay) {
|
||||
var x = event.x - pos.x - margin - locationWidth;
|
||||
print(x);
|
||||
if (x < SCALE_MINUS) {
|
||||
self.decreaseScale();
|
||||
} else if (x > SCALE_PLUS) {
|
||||
self.increaseScale();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.close = function() {
|
||||
this.cleanup();
|
||||
this._onClose();
|
||||
};
|
||||
|
||||
this.cleanup = function() {
|
||||
Overlays.deleteOverlay(background);
|
||||
Overlays.deleteOverlay(titleText);
|
||||
Overlays.deleteOverlay(locationButton);
|
||||
Overlays.deleteOverlay(exportButton);
|
||||
Overlays.deleteOverlay(cancelButton);
|
||||
Overlays.deleteOverlay(voxelPreview);
|
||||
Overlays.deleteOverlay(scaleOverlay);
|
||||
Overlays.deleteOverlay(scaleView);
|
||||
};
|
||||
|
||||
print("CONNECTING!");
|
||||
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
|
||||
};
|
||||
|
||||
var ModelImporter = function(opts) {
|
||||
var self = this;
|
||||
|
||||
var height = 30;
|
||||
var margin = 4;
|
||||
var outerHeight = height + (2 * margin);
|
||||
var titleWidth = 120;
|
||||
var cancelWidth = 100;
|
||||
var fullWidth = titleWidth + cancelWidth + (2 * margin);
|
||||
|
||||
var localModels = Overlays.addOverlay("localmodels", {
|
||||
position: { x: 1, y: 1, z: 1 },
|
||||
scale: 1,
|
||||
visible: false
|
||||
});
|
||||
var importScale = 1;
|
||||
var importBoundaries = Overlays.addOverlay("cube", {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
size: 1,
|
||||
color: { red: 128, blue: 128, green: 128 },
|
||||
lineWidth: 4,
|
||||
solid: false,
|
||||
visible: false
|
||||
});
|
||||
|
||||
var pos = { x: windowDimensions.x / 2 - (fullWidth / 2), y: windowDimensions.y - 100 };
|
||||
|
||||
var background = Overlays.addOverlay("text", {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
opacity: 1,
|
||||
width: fullWidth,
|
||||
height: outerHeight,
|
||||
backgroundColor: { red: 200, green: 200, blue: 200 },
|
||||
visible: false,
|
||||
text: "",
|
||||
});
|
||||
|
||||
var titleText = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin,
|
||||
y: pos.y + margin,
|
||||
font: { size: 14 },
|
||||
width: titleWidth,
|
||||
height: height,
|
||||
backgroundColor: { red: 255, green: 255, blue: 255 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
visible: false,
|
||||
text: "Import Models"
|
||||
});
|
||||
var cancelButton = Overlays.addOverlay("text", {
|
||||
x: pos.x + margin + titleWidth,
|
||||
y: pos.y + margin,
|
||||
width: cancelWidth,
|
||||
height: height,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
visible: false,
|
||||
text: "Close"
|
||||
});
|
||||
this._importing = false;
|
||||
|
||||
this.setImportVisible = function(visible) {
|
||||
Overlays.editOverlay(importBoundaries, { visible: visible });
|
||||
Overlays.editOverlay(localModels, { visible: visible });
|
||||
Overlays.editOverlay(cancelButton, { visible: visible });
|
||||
Overlays.editOverlay(titleText, { visible: visible });
|
||||
Overlays.editOverlay(background, { visible: visible });
|
||||
};
|
||||
|
||||
var importPosition = { x: 0, y: 0, z: 0 };
|
||||
this.moveImport = function(position) {
|
||||
importPosition = position;
|
||||
Overlays.editOverlay(localModels, {
|
||||
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
|
||||
});
|
||||
Overlays.editOverlay(importBoundaries, {
|
||||
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
|
||||
});
|
||||
}
|
||||
|
||||
this.mouseMoveEvent = function(event) {
|
||||
if (self._importing) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var intersection = Voxels.findRayIntersection(pickRay);
|
||||
|
||||
var distance = 2;// * self._scale;
|
||||
|
||||
if (false) {//intersection.intersects) {
|
||||
var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection));
|
||||
if (intersectionDistance < distance) {
|
||||
distance = intersectionDistance * 0.99;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var targetPosition = {
|
||||
x: pickRay.origin.x + (pickRay.direction.x * distance),
|
||||
y: pickRay.origin.y + (pickRay.direction.y * distance),
|
||||
z: pickRay.origin.z + (pickRay.direction.z * distance)
|
||||
};
|
||||
|
||||
if (targetPosition.x < 0) targetPosition.x = 0;
|
||||
if (targetPosition.y < 0) targetPosition.y = 0;
|
||||
if (targetPosition.z < 0) targetPosition.z = 0;
|
||||
|
||||
var nudgeFactor = 1;
|
||||
var newPosition = {
|
||||
x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor,
|
||||
y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor,
|
||||
z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor
|
||||
}
|
||||
|
||||
self.moveImport(newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
this.mouseReleaseEvent = function(event) {
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
||||
if (clickedOverlay == cancelButton) {
|
||||
self._importing = false;
|
||||
self.setImportVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Would prefer to use {4} for the coords, but it would only capture the last digit.
|
||||
var fileRegex = /__(.+)__(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)__/;
|
||||
this.doImport = function() {
|
||||
if (!self._importing) {
|
||||
var filename = Window.browse("Select models to import", "", "*.svo")
|
||||
if (filename) {
|
||||
parts = fileRegex.exec(filename);
|
||||
if (parts == null) {
|
||||
Window.alert("The file you selected does not contain source domain or location information");
|
||||
} else {
|
||||
var hostname = parts[1];
|
||||
var x = parts[2];
|
||||
var y = parts[3];
|
||||
var z = parts[4];
|
||||
var s = parts[5];
|
||||
importScale = s;
|
||||
if (hostname != location.hostname) {
|
||||
if (!Window.confirm(("These models were not originally exported from this domain. Continue?"))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (Window.confirm(("Would you like to import back to the source location?"))) {
|
||||
var success = Clipboard.importModels(filename);
|
||||
if (success) {
|
||||
Clipboard.pasteModels(x, y, z, 1);
|
||||
} else {
|
||||
Window.alert("There was an error importing the model file.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var success = Clipboard.importModels(filename);
|
||||
if (success) {
|
||||
self._importing = true;
|
||||
self.setImportVisible(true);
|
||||
Overlays.editOverlay(importBoundaries, { size: s });
|
||||
} else {
|
||||
Window.alert("There was an error importing the model file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.paste = function() {
|
||||
if (self._importing) {
|
||||
// self._importing = false;
|
||||
// self.setImportVisible(false);
|
||||
Clipboard.pasteModels(importPosition.x, importPosition.y, importPosition.z, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
Overlays.deleteOverlay(localModels);
|
||||
Overlays.deleteOverlay(importBoundaries);
|
||||
Overlays.deleteOverlay(cancelButton);
|
||||
Overlays.deleteOverlay(titleText);
|
||||
Overlays.deleteOverlay(background);
|
||||
}
|
||||
|
||||
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.connect(this.mouseMoveEvent);
|
||||
};
|
||||
|
||||
var modelImporter = new ModelImporter();
|
||||
|
||||
function isLocked(properties) {
|
||||
// special case to lock the ground plane model in hq.
|
||||
if (location.hostname == "hq.highfidelity.io" &&
|
||||
|
@ -644,6 +1051,11 @@ function checkController(deltaTime) {
|
|||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
||||
if (!isActive) {
|
||||
// So that we hide the lasers bellow and keep updating the overlays position
|
||||
numberOfButtons = 0;
|
||||
}
|
||||
|
||||
// this is expected for hydras
|
||||
if (numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2) {
|
||||
|
@ -665,11 +1077,21 @@ function checkController(deltaTime) {
|
|||
|
||||
moveOverlays();
|
||||
}
|
||||
|
||||
var isActive = false;
|
||||
var active;
|
||||
var newModel;
|
||||
var browser;
|
||||
function initToolBar() {
|
||||
toolBar = new ToolBar(0, 0, ToolBar.VERTICAL);
|
||||
// New Model
|
||||
active = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "models-tool.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: toolWidth, height: toolHeight,
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
}, true, false);
|
||||
newModel = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "add-model-tool.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
|
@ -787,6 +1209,11 @@ function mousePressEvent(event) {
|
|||
modelSelected = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
||||
if (active == toolBar.clicked(clickedOverlay)) {
|
||||
isActive = !isActive;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newModel == toolBar.clicked(clickedOverlay)) {
|
||||
var url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]);
|
||||
if (url == null || url == "") {
|
||||
|
@ -822,6 +1249,11 @@ function mousePressEvent(event) {
|
|||
}
|
||||
|
||||
} else {
|
||||
// If we aren't active and didn't click on an overlay: quit
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
Vec3.print("[Mouse] Looking at: ", pickRay.origin);
|
||||
var foundIntersection = Models.findRayIntersection(pickRay);
|
||||
|
@ -906,7 +1338,7 @@ var oldModifier = 0;
|
|||
var modifier = 0;
|
||||
var wasShifted = false;
|
||||
function mouseMoveEvent(event) {
|
||||
if (event.isAlt) {
|
||||
if (event.isAlt || !isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1049,7 +1481,7 @@ function mouseMoveEvent(event) {
|
|||
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (event.isAlt) {
|
||||
if (event.isAlt || !isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1069,7 +1501,7 @@ function mouseReleaseEvent(event) {
|
|||
var modelMenuAddedDelete = false;
|
||||
function setupModelMenus() {
|
||||
print("setupModelMenus()");
|
||||
// add our menuitems
|
||||
// adj our menuitems
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...",
|
||||
shortcutKeyEvent: { text: "`" }, afterItem: "Models" });
|
||||
|
@ -1081,6 +1513,12 @@ function setupModelMenus() {
|
|||
} else {
|
||||
print("delete exists... don't add ours");
|
||||
}
|
||||
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
|
||||
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
|
||||
}
|
||||
|
||||
function cleanupModelMenus() {
|
||||
|
@ -1090,6 +1528,12 @@ function cleanupModelMenus() {
|
|||
// delete our menuitems
|
||||
Menu.removeMenuItem("Edit", "Delete");
|
||||
}
|
||||
|
||||
Menu.removeMenuItem("Edit", "Paste Models");
|
||||
|
||||
Menu.removeSeparator("File", "Models");
|
||||
Menu.removeMenuItem("File", "Export Models");
|
||||
Menu.removeMenuItem("File", "Import Models");
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
|
@ -1098,6 +1542,10 @@ function scriptEnding() {
|
|||
toolBar.cleanup();
|
||||
cleanupModelMenus();
|
||||
tooltip.cleanup();
|
||||
modelImporter.cleanup();
|
||||
if (exportMenu) {
|
||||
exportMenu.close();
|
||||
}
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
|
@ -1175,6 +1623,18 @@ function handeMenuEvent(menuItem){
|
|||
|
||||
Models.editModel(selectedModelID, selectedModelProperties);
|
||||
}
|
||||
} else if (menuItem == "Paste Models") {
|
||||
modelImporter.paste();
|
||||
} else if (menuItem == "Export Models") {
|
||||
if (!exportMenu) {
|
||||
exportMenu = new ExportMenu({
|
||||
onClose: function() {
|
||||
exportMenu = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (menuItem == "Import Models") {
|
||||
modelImporter.doImport();
|
||||
}
|
||||
tooltip.show(false);
|
||||
}
|
||||
|
@ -1221,4 +1681,4 @@ Controller.keyReleaseEvent.connect(function(event) {
|
|||
if (event.text == "BACKSPACE") {
|
||||
handeMenuEvent("Delete");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
272
examples/rockPaperScissorsCells.js
Normal file
272
examples/rockPaperScissorsCells.js
Normal file
|
@ -0,0 +1,272 @@
|
|||
// rockPaperScissorsCells.js
|
||||
// examples
|
||||
//
|
||||
// Created by Ben Arnold on 7/16/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This sample script creates a voxel wall that simulates the Rock Paper Scissors cellular
|
||||
// automata. http://www.gamedev.net/blog/844/entry-2249737-another-cellular-automaton-video/
|
||||
// If multiple instances of this script are run, they will combine into a larger wall.
|
||||
// NOTE: You must run each instance one at a time. If they all start at once there are race conditions.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var NUMBER_OF_CELLS_EACH_DIMENSION = 48;
|
||||
var NUMBER_OF_CELLS_REGION_EACH_DIMESION = 16;
|
||||
var REGIONS_EACH_DIMENSION = NUMBER_OF_CELLS_EACH_DIMENSION / NUMBER_OF_CELLS_REGION_EACH_DIMESION;
|
||||
|
||||
var isLocal = false;
|
||||
|
||||
var currentCells = [];
|
||||
var nextCells = [];
|
||||
|
||||
var cornerPosition = {x: 100, y: 0, z: 0 }
|
||||
var position = {x: 0, y: 0, z: 0 };
|
||||
|
||||
var METER_LENGTH = 1;
|
||||
var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION;
|
||||
|
||||
var viewerPosition = {x: cornerPosition.x + (NUMBER_OF_CELLS_EACH_DIMENSION / 2) * cellScale, y: cornerPosition.y + (NUMBER_OF_CELLS_EACH_DIMENSION / 2) * cellScale, z: cornerPosition.z };
|
||||
|
||||
viewerPosition.z += 50;
|
||||
var yaw = 0;
|
||||
var orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0);
|
||||
|
||||
//Feel free to add new cell types here. It can be more than three.
|
||||
var cellTypes = [];
|
||||
cellTypes[0] = { r: 255, g: 0, b: 0 };
|
||||
cellTypes[1] = { r: 0, g: 255, b: 0 };
|
||||
cellTypes[2] = { r: 0, g:0, b: 255 };
|
||||
cellTypes[3] = { r: 0, g: 255, b: 255 };
|
||||
|
||||
|
||||
//Check for free region for AC
|
||||
var regionMarkerX = -1;
|
||||
var regionMarkerY = -1;
|
||||
var regionMarkerI = -1;
|
||||
var regionMarkerJ = -1;
|
||||
|
||||
var regionMarkerColor = {r: 254, g: 0, b: 253};
|
||||
|
||||
function setRegionToColor(startX, startY, width, height, color) {
|
||||
for (var i = startY; i < startY + height; i++) {
|
||||
for (var j = startX; j < startX + width; j++) {
|
||||
|
||||
currentCells[i][j] = { changed: true, type: color };
|
||||
|
||||
// put the same value in the nextCells array for first board draw
|
||||
nextCells[i][j] = { changed: true, type: color };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
for (var i = 0; i < REGIONS_EACH_DIMENSION; i++) {
|
||||
for (var j = 0; j < REGIONS_EACH_DIMENSION; j++) {
|
||||
var x = cornerPosition.x + (j) * cellScale;
|
||||
var y = cornerPosition.y + (i + NUMBER_OF_CELLS_EACH_DIMENSION) * cellScale;
|
||||
var z = cornerPosition.z;
|
||||
var voxel = Voxels.getVoxelAt(x, y, z, cellScale);
|
||||
if (voxel.x != x || voxel.y != y || voxel.z != z || voxel.s != cellScale ||
|
||||
voxel.red != regionMarkerColor.r || voxel.green != regionMarkerColor.g || voxel.blue != regionMarkerColor.b) {
|
||||
regionMarkerX = x;
|
||||
regionMarkerY = y;
|
||||
regionMarkerI = i;
|
||||
regionMarkerJ = j;
|
||||
i = REGIONS_EACH_DIMENSION; //force quit loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Didnt find an open spot, end script
|
||||
if (regionMarkerX == -1) {
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
position.x = cornerPosition.x + regionMarkerJ * NUMBER_OF_CELLS_REGION_EACH_DIMESION;
|
||||
position.y = cornerPosition.y + regionMarkerI * NUMBER_OF_CELLS_REGION_EACH_DIMESION;
|
||||
position.z = cornerPosition.z;
|
||||
|
||||
Voxels.setVoxel(regionMarkerX, regionMarkerY, cornerPosition.z, cellScale, regionMarkerColor.r, regionMarkerColor.g, regionMarkerColor.b);
|
||||
|
||||
for (var i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
|
||||
// create the array to hold this row
|
||||
currentCells[i] = [];
|
||||
|
||||
// create the array to hold this row in the nextCells array
|
||||
nextCells[i] = [];
|
||||
}
|
||||
|
||||
var width = NUMBER_OF_CELLS_REGION_EACH_DIMESION / 2;
|
||||
setRegionToColor(0, 0, width, width, 0);
|
||||
setRegionToColor(0, width, width, width, 1);
|
||||
setRegionToColor(width, width, width, width, 2);
|
||||
setRegionToColor(width, 0, width, width, 3);
|
||||
}
|
||||
|
||||
function updateCells() {
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
var cell;
|
||||
var y = 0;
|
||||
var x = 0;
|
||||
|
||||
for (i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
|
||||
for (j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) {
|
||||
|
||||
cell = currentCells[i][j];
|
||||
|
||||
var r = Math.floor(Math.random() * 8);
|
||||
|
||||
switch (r){
|
||||
case 0:
|
||||
y = i - 1;
|
||||
x = j - 1;
|
||||
break;
|
||||
case 1:
|
||||
y = i;
|
||||
x = j-1;
|
||||
break;
|
||||
case 2:
|
||||
y = i + 1;
|
||||
x = j - 1;
|
||||
break;
|
||||
case 3:
|
||||
y = i + 1;
|
||||
x = j;
|
||||
break;
|
||||
case 4:
|
||||
y = i + 1;
|
||||
x = j + 1;
|
||||
break;
|
||||
case 5:
|
||||
y = i;
|
||||
x = j + 1;
|
||||
break;
|
||||
case 6:
|
||||
y = i - 1;
|
||||
x = j + 1;
|
||||
break;
|
||||
case 7:
|
||||
y = i - 1;
|
||||
x = j;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
//check the voxel grid instead of local array when on the edge
|
||||
if (x == -1 || x == NUMBER_OF_CELLS_REGION_EACH_DIMESION ||
|
||||
y == -1 || y == NUMBER_OF_CELLS_REGION_EACH_DIMESION) {
|
||||
|
||||
var voxel = Voxels.getVoxelAt(position.x + x * cellScale, position.y + y * cellScale, position.z, cellScale);
|
||||
var predatorCellType = ((cell.type + 1) % cellTypes.length);
|
||||
var predatorCellColor = cellTypes[predatorCellType];
|
||||
if (voxel.red == predatorCellColor.r && voxel.green == predatorCellColor.g && voxel.blue == predatorCellColor.b) {
|
||||
nextCells[i][j].type = predatorCellType;
|
||||
nextCells[i][j].changed = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
if (currentCells[y][x].type == ((cell.type + 1) % cellTypes.length)) {
|
||||
nextCells[i][j].type = currentCells[y][x].type;
|
||||
nextCells[i][j].changed = true;
|
||||
} else {
|
||||
//indicate no update
|
||||
nextCells[i][j].changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
|
||||
for (j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) {
|
||||
if (nextCells[i][j].changed == true) {
|
||||
// there has been a change to this cell, change the value in the currentCells array
|
||||
currentCells[i][j].type = nextCells[i][j].type;
|
||||
currentCells[i][j].changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendNextCells() {
|
||||
for (var i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
|
||||
for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) {
|
||||
if (nextCells[i][j].changed == true) {
|
||||
// there has been a change to the state of this cell, send it
|
||||
|
||||
// find the x and y position for this voxel, z = 0
|
||||
var x = j * cellScale;
|
||||
var y = i * cellScale;
|
||||
var type = nextCells[i][j].type;
|
||||
|
||||
// queue a packet to add a voxel for the new cell
|
||||
Voxels.setVoxel(position.x + x, position.y + y, position.z, cellScale, cellTypes[type].r, cellTypes[type].g, cellTypes[type].b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sentFirstBoard = false;
|
||||
var voxelViewerInit = false;
|
||||
|
||||
var UPDATES_PER_SECOND = 6.0;
|
||||
var frameIndex = 1.0;
|
||||
var oldFrameIndex = 0;
|
||||
|
||||
var framesToWait = UPDATES_PER_SECOND;
|
||||
|
||||
function step(deltaTime) {
|
||||
|
||||
if (isLocal == false) {
|
||||
if (voxelViewerInit == false) {
|
||||
VoxelViewer.setPosition(viewerPosition);
|
||||
VoxelViewer.setOrientation(orientation);
|
||||
voxelViewerInit = true;
|
||||
}
|
||||
VoxelViewer.queryOctree();
|
||||
}
|
||||
|
||||
frameIndex += deltaTime * UPDATES_PER_SECOND;
|
||||
if (Math.floor(frameIndex) == oldFrameIndex) {
|
||||
return;
|
||||
}
|
||||
oldFrameIndex++;
|
||||
|
||||
if (frameIndex <= framesToWait) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sentFirstBoard) {
|
||||
// we've already sent the first full board, perform a step in time
|
||||
updateCells();
|
||||
} else {
|
||||
// this will be our first board send
|
||||
sentFirstBoard = true;
|
||||
init();
|
||||
}
|
||||
|
||||
if (isLocal == false) {
|
||||
VoxelViewer.queryOctree();
|
||||
}
|
||||
sendNextCells();
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
Voxels.eraseVoxel(regionMarkerX, regionMarkerY, position.z, cellScale);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
Script.update.connect(step);
|
||||
Voxels.setPacketsPerSecond(2000);
|
||||
|
||||
// test for local...
|
||||
Menu.isOptionChecked("Voxels");
|
||||
isLocal = true; // will only get here on local client
|
|
@ -140,6 +140,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_voxelImporter(NULL),
|
||||
_importSucceded(false),
|
||||
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
|
||||
_modelClipboardRenderer(),
|
||||
_modelClipboard(),
|
||||
_wantToKillLocalVoxels(false),
|
||||
_viewFrustum(),
|
||||
_lastQueriedViewFrustum(),
|
||||
|
@ -174,6 +176,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_lastNackTime(usecTimestampNow()),
|
||||
_lastSendDownstreamAudioStats(usecTimestampNow())
|
||||
{
|
||||
|
||||
// read the ApplicationInfo.ini file for Name/Version/Domain information
|
||||
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
|
||||
|
||||
|
@ -1509,6 +1512,33 @@ struct SendVoxelsOperationArgs {
|
|||
const unsigned char* newBaseOctCode;
|
||||
};
|
||||
|
||||
bool Application::exportModels(const QString& filename, float x, float y, float z, float scale) {
|
||||
QVector<ModelItem*> models;
|
||||
_models.getTree()->findModelsInCube(AACube(glm::vec3(x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), models);
|
||||
if (models.size() > 0) {
|
||||
glm::vec3 root(x, y, z);
|
||||
ModelTree exportTree;
|
||||
|
||||
for (int i = 0; i < models.size(); i++) {
|
||||
ModelItemProperties properties;
|
||||
ModelItemID id = models.at(i)->getModelItemID();
|
||||
id.isKnownID = false;
|
||||
properties.copyFromNewModelItem(*models.at(i));
|
||||
properties.setPosition(properties.getPosition() - root);
|
||||
exportTree.addModel(id, properties);
|
||||
}
|
||||
|
||||
exportTree.writeToSVOFile(filename.toLocal8Bit().constData());
|
||||
} else {
|
||||
qDebug() << "No models were selected";
|
||||
return false;
|
||||
}
|
||||
|
||||
// restore the main window's active state
|
||||
_window->activateWindow();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) {
|
||||
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||
SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData;
|
||||
|
@ -1590,6 +1620,19 @@ void Application::importVoxels() {
|
|||
emit importDone();
|
||||
}
|
||||
|
||||
bool Application::importModels(const QString& filename) {
|
||||
_modelClipboard.eraseAllOctreeElements();
|
||||
bool success = _modelClipboard.readFromSVOFile(filename.toLocal8Bit().constData());
|
||||
if (success) {
|
||||
_modelClipboard.reaverageOctreeElements();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void Application::pasteModels(float x, float y, float z) {
|
||||
_modelClipboard.sendModels(&_modelEditSender, x, y, z);
|
||||
}
|
||||
|
||||
void Application::cutVoxels(const VoxelDetail& sourceVoxel) {
|
||||
copyVoxels(sourceVoxel);
|
||||
deleteVoxelAt(sourceVoxel);
|
||||
|
@ -1753,6 +1796,10 @@ void Application::init() {
|
|||
_models.init();
|
||||
_models.setViewFrustum(getViewFrustum());
|
||||
|
||||
_modelClipboardRenderer.init();
|
||||
_modelClipboardRenderer.setViewFrustum(getViewFrustum());
|
||||
_modelClipboardRenderer.setTree(&_modelClipboard);
|
||||
|
||||
_metavoxels.init();
|
||||
|
||||
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
|
||||
|
@ -3674,6 +3721,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
|
||||
connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(scriptFinished(const QString&)));
|
||||
|
||||
connect(scriptEngine, SIGNAL(loadScript(const QString&)), this, SLOT(loadScript(const QString&)));
|
||||
|
||||
scriptEngine->registerGlobalObject("Overlays", &_overlays);
|
||||
|
||||
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
|
||||
|
|
|
@ -201,6 +201,8 @@ public:
|
|||
bool getImportSucceded() { return _importSucceded; }
|
||||
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
|
||||
VoxelTree* getClipboard() { return &_clipboard; }
|
||||
ModelTree* getModelClipboard() { return &_modelClipboard; }
|
||||
ModelTreeRenderer* getModelClipboardRenderer() { return &_modelClipboardRenderer; }
|
||||
Environment* getEnvironment() { return &_environment; }
|
||||
bool isMousePressed() const { return _mousePressed; }
|
||||
bool isMouseHidden() const { return _mouseHidden; }
|
||||
|
@ -227,6 +229,7 @@ public:
|
|||
float getPacketsPerSecond() const { return _packetsPerSecond; }
|
||||
float getBytesPerSecond() const { return _bytesPerSecond; }
|
||||
const glm::vec3& getViewMatrixTranslation() const { return _viewMatrixTranslation; }
|
||||
void setViewMatrixTranslation(const glm::vec3& translation) { _viewMatrixTranslation = translation; }
|
||||
|
||||
/// if you need to access the application settings, use lockSettings()/unlockSettings()
|
||||
QSettings* lockSettings() { _settingsMutex.lock(); return _settings; }
|
||||
|
@ -314,6 +317,10 @@ public slots:
|
|||
void nodeKilled(SharedNodePointer node);
|
||||
void packetSent(quint64 length);
|
||||
|
||||
void pasteModels(float x, float y, float z);
|
||||
bool exportModels(const QString& filename, float x, float y, float z, float scale);
|
||||
bool importModels(const QString& filename);
|
||||
|
||||
void importVoxels(); // doesn't include source voxel because it goes to clipboard
|
||||
void cutVoxels(const VoxelDetail& sourceVoxel);
|
||||
void copyVoxels(const VoxelDetail& sourceVoxel);
|
||||
|
@ -462,6 +469,8 @@ private:
|
|||
ParticleCollisionSystem _particleCollisionSystem;
|
||||
|
||||
ModelTreeRenderer _models;
|
||||
ModelTreeRenderer _modelClipboardRenderer;
|
||||
ModelTree _modelClipboard;
|
||||
|
||||
QByteArray _voxelsFilename;
|
||||
bool _wantToKillLocalVoxels;
|
||||
|
|
|
@ -724,7 +724,7 @@ void Audio::handleAudioInput() {
|
|||
delete[] inputAudioSamples;
|
||||
}
|
||||
|
||||
if (_receivedAudioStream.getPacketReceived() > 0) {
|
||||
if (_receivedAudioStream.getPacketsReceived() > 0) {
|
||||
pushAudioToOutput();
|
||||
}
|
||||
}
|
||||
|
@ -1460,9 +1460,9 @@ void Audio::renderAudioStreamStats(const AudioStreamStats& streamStats, int hori
|
|||
|
||||
sprintf(stringBuffer, " Packet loss | overall: %5.2f%% (%d lost), last_30s: %5.2f%% (%d lost)",
|
||||
streamStats._packetStreamStats.getLostRate() * 100.0f,
|
||||
streamStats._packetStreamStats._numLost,
|
||||
streamStats._packetStreamStats._lost,
|
||||
streamStats._packetStreamWindowStats.getLostRate() * 100.0f,
|
||||
streamStats._packetStreamWindowStats._numLost);
|
||||
streamStats._packetStreamWindowStats._lost);
|
||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ static QString sampleJson = "[{\"id\":1, \
|
|||
static const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.0f);
|
||||
static const float TRANSLATION_SCALE = 1.0f;
|
||||
static const int NUM_BLENDSHAPE_COEFF = 30;
|
||||
static const int NUM_SMOOTHING_SAMPLES = 3;
|
||||
|
||||
struct CaraPerson {
|
||||
struct CaraPose {
|
||||
|
@ -217,9 +218,9 @@ private:
|
|||
|
||||
CaraFaceTracker::CaraFaceTracker() :
|
||||
_lastReceiveTimestamp(0),
|
||||
_previousPitch(0.0f),
|
||||
_previousYaw(0.0f),
|
||||
_previousRoll(0.0f),
|
||||
_pitchAverage(NUM_SMOOTHING_SAMPLES),
|
||||
_yawAverage(NUM_SMOOTHING_SAMPLES),
|
||||
_rollAverage(NUM_SMOOTHING_SAMPLES),
|
||||
_eyeGazeLeftPitch(0.0f),
|
||||
_eyeGazeLeftYaw(0.0f),
|
||||
_eyeGazeRightPitch(0.0f),
|
||||
|
@ -252,9 +253,9 @@ CaraFaceTracker::CaraFaceTracker() :
|
|||
|
||||
CaraFaceTracker::CaraFaceTracker(const QHostAddress& host, quint16 port) :
|
||||
_lastReceiveTimestamp(0),
|
||||
_previousPitch(0.0f),
|
||||
_previousYaw(0.0f),
|
||||
_previousRoll(0.0f),
|
||||
_pitchAverage(NUM_SMOOTHING_SAMPLES),
|
||||
_yawAverage(NUM_SMOOTHING_SAMPLES),
|
||||
_rollAverage(NUM_SMOOTHING_SAMPLES),
|
||||
_eyeGazeLeftPitch(0.0f),
|
||||
_eyeGazeLeftYaw(0.0f),
|
||||
_eyeGazeRightPitch(0.0f),
|
||||
|
@ -371,6 +372,7 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) {
|
|||
CaraPerson person = CaraPacketDecoder::extractOne(buffer, &jsonError);
|
||||
|
||||
if(jsonError.error == QJsonParseError::NoError) {
|
||||
|
||||
//do some noise filtering to the head poses
|
||||
//reduce the noise first by truncating to 1 dp
|
||||
person.pose.roll = glm::floor(person.pose.roll * 10) / 10;
|
||||
|
@ -386,43 +388,39 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) {
|
|||
float theta = 2 * acos(r.w);
|
||||
if (theta > EPSILON) {
|
||||
float rMag = glm::length(glm::vec3(r.x, r.y, r.z));
|
||||
const float AVERAGE_CARA_FRAME_TIME = 0.033f;
|
||||
const float AVERAGE_CARA_FRAME_TIME = 0.04f;
|
||||
const float ANGULAR_VELOCITY_MIN = 1.2f;
|
||||
const float YAW_STANDARD_DEV_DEG = 2.5f;
|
||||
|
||||
_headAngularVelocity = theta / AVERAGE_CARA_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag;
|
||||
_pitchAverage.updateAverage(person.pose.pitch);
|
||||
_rollAverage.updateAverage(person.pose.roll);
|
||||
|
||||
//use the angular velocity for roll and pitch, if it's below the threshold don't move
|
||||
if(glm::abs(_headAngularVelocity.x) < ANGULAR_VELOCITY_MIN) {
|
||||
person.pose.pitch = _previousPitch;
|
||||
}
|
||||
//could use the angular velocity to detemine whether to update pitch and roll to further remove the noise.
|
||||
//use the angular velocity for roll and pitch, update if > THRESHOLD
|
||||
//if(glm::abs(_headAngularVelocity.x) > ANGULAR_VELOCITY_MIN) {
|
||||
// _pitchAverage.updateAverage(person.pose.pitch);
|
||||
//}
|
||||
|
||||
if(glm::abs(_headAngularVelocity.z) < ANGULAR_VELOCITY_MIN) {
|
||||
person.pose.roll = _previousRoll;
|
||||
}
|
||||
//if(glm::abs(_headAngularVelocity.z) > ANGULAR_VELOCITY_MIN) {
|
||||
// _rollAverage.updateAverage(person.pose.roll);;
|
||||
//}
|
||||
|
||||
//for yaw, the jitter is great, you can't use angular velocity because it swings too much
|
||||
//use the previous and current yaw, calculate the
|
||||
//abs difference and move it the difference is above the standard deviation which is around 2.5
|
||||
// (this will introduce some jerks but will not encounter lag)
|
||||
|
||||
// < the standard deviation 2.5 deg, no move
|
||||
if(glm::abs(person.pose.yaw - _previousYaw) < YAW_STANDARD_DEV_DEG) {
|
||||
// > the standard deviation 2.5 deg, update the yaw smoothing average
|
||||
if(glm::abs(person.pose.yaw - _yawAverage.getAverage()) > YAW_STANDARD_DEV_DEG) {
|
||||
//qDebug() << "Yaw Diff: " << glm::abs(person.pose.yaw - _previousYaw);
|
||||
person.pose.yaw = _previousYaw;
|
||||
_yawAverage.updateAverage(person.pose.yaw);
|
||||
}
|
||||
|
||||
//update the previous angles
|
||||
_previousPitch = person.pose.pitch;
|
||||
_previousYaw = person.pose.yaw;
|
||||
_previousRoll = person.pose.roll;
|
||||
|
||||
//set the new rotation
|
||||
newRotation = glm::quat(glm::vec3(DEGTORAD(person.pose.pitch), DEGTORAD(person.pose.yaw), DEGTORAD(-person.pose.roll)));
|
||||
newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage())));
|
||||
}
|
||||
else {
|
||||
//no change in position
|
||||
newRotation = glm::quat(glm::vec3(DEGTORAD(_previousPitch), DEGTORAD(_previousYaw), DEGTORAD(-_previousRoll)));
|
||||
//no change in position, use previous averages
|
||||
newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage())));
|
||||
_headAngularVelocity = glm::vec3(0,0,0);
|
||||
}
|
||||
|
||||
|
@ -456,4 +454,3 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) {
|
|||
float CaraFaceTracker::getBlendshapeCoefficient(int index) const {
|
||||
return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include "FaceTracker.h"
|
||||
|
||||
/*!
|
||||
|
@ -90,10 +91,10 @@ private:
|
|||
//head tracking
|
||||
glm::vec3 _headAngularVelocity;
|
||||
|
||||
//pose history
|
||||
float _previousPitch;
|
||||
float _previousYaw;
|
||||
float _previousRoll;
|
||||
//pose average
|
||||
SimpleMovingAverage _pitchAverage;
|
||||
SimpleMovingAverage _yawAverage;
|
||||
SimpleMovingAverage _rollAverage;
|
||||
|
||||
// eye gaze degrees
|
||||
float _eyeGazeLeftPitch;
|
||||
|
|
|
@ -100,3 +100,16 @@ void ClipboardScriptingInterface::nudgeVoxel(float x, float y, float z, float s,
|
|||
|
||||
Application::getInstance()->nudgeVoxelsByVector(sourceVoxel, nudgeVecInTreeSpace);
|
||||
}
|
||||
|
||||
|
||||
bool ClipboardScriptingInterface::exportModels(const QString& filename, float x, float y, float z, float s) {
|
||||
return Application::getInstance()->exportModels(filename, x, y, z, s);
|
||||
}
|
||||
|
||||
bool ClipboardScriptingInterface::importModels(const QString& filename) {
|
||||
return Application::getInstance()->importModels(filename);
|
||||
}
|
||||
|
||||
void ClipboardScriptingInterface::pasteModels(float x, float y, float z, float s) {
|
||||
Application::getInstance()->pasteModels(x, y, z);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,10 @@ public slots:
|
|||
|
||||
void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
|
||||
void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec);
|
||||
|
||||
bool importModels(const QString& filename);
|
||||
bool exportModels(const QString& filename, float x, float y, float z, float s);
|
||||
void pasteModels(float x, float y, float z, float s);
|
||||
};
|
||||
|
||||
#endif // hifi_ClipboardScriptingInterface_h
|
||||
|
|
|
@ -67,6 +67,15 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin
|
|||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QScriptValue, retVal),
|
||||
Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter),
|
||||
Q_ARG(QFileDialog::AcceptMode, QFileDialog::AcceptSave));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::s3Browse(const QString& nameFilter) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showS3Browse", Qt::BlockingQueuedConnection,
|
||||
|
@ -182,18 +191,26 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const
|
|||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
|
||||
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
|
||||
QFileDialog::AcceptMode acceptMode) {
|
||||
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
|
||||
// filename if the directory is valid.
|
||||
QString path = "";
|
||||
QFileInfo fileInfo = QFileInfo(directory);
|
||||
qDebug() << "File: " << directory << fileInfo.isFile();
|
||||
if (fileInfo.isDir()) {
|
||||
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
|
||||
path = fileInfo.filePath();
|
||||
}
|
||||
|
||||
QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter);
|
||||
fileDialog.setFileMode(QFileDialog::ExistingFile);
|
||||
fileDialog.setAcceptMode(acceptMode);
|
||||
qDebug() << "Opening!";
|
||||
QUrl fileUrl(directory);
|
||||
if (acceptMode == QFileDialog::AcceptSave) {
|
||||
fileDialog.setFileMode(QFileDialog::Directory);
|
||||
fileDialog.selectFile(fileUrl.fileName());
|
||||
}
|
||||
if (fileDialog.exec()) {
|
||||
return QScriptValue(fileDialog.selectedFiles().first());
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ public slots:
|
|||
QScriptValue form(const QString& title, QScriptValue array);
|
||||
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue s3Browse(const QString& nameFilter = "");
|
||||
|
||||
private slots:
|
||||
|
@ -38,7 +39,8 @@ private slots:
|
|||
QScriptValue showConfirm(const QString& message);
|
||||
QScriptValue showForm(const QString& title, QScriptValue form);
|
||||
QScriptValue showPrompt(const QString& message, const QString& defaultText);
|
||||
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter);
|
||||
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
|
||||
QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen);
|
||||
QScriptValue showS3Browse(const QString& nameFilter);
|
||||
|
||||
private:
|
||||
|
|
|
@ -366,13 +366,13 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
|
|||
QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
|
||||
QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
|
||||
const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats();
|
||||
QString incomingOutOfOrderString = locale.toString((uint)seqStats.getNumOutOfOrder());
|
||||
QString incomingLateString = locale.toString((uint)seqStats.getNumLate());
|
||||
QString incomingUnreasonableString = locale.toString((uint)seqStats.getNumUnreasonable());
|
||||
QString incomingEarlyString = locale.toString((uint)seqStats.getNumEarly());
|
||||
QString incomingLikelyLostString = locale.toString((uint)seqStats.getNumLost());
|
||||
QString incomingRecovered = locale.toString((uint)seqStats.getNumRecovered());
|
||||
QString incomingDuplicateString = locale.toString((uint)seqStats.getNumDuplicate());
|
||||
QString incomingOutOfOrderString = locale.toString((uint)seqStats.getOutOfOrder());
|
||||
QString incomingLateString = locale.toString((uint)seqStats.getLate());
|
||||
QString incomingUnreasonableString = locale.toString((uint)seqStats.getUnreasonable());
|
||||
QString incomingEarlyString = locale.toString((uint)seqStats.getEarly());
|
||||
QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost());
|
||||
QString incomingRecovered = locale.toString((uint)seqStats.getRecovered());
|
||||
QString incomingDuplicateString = locale.toString((uint)seqStats.getDuplicate());
|
||||
|
||||
int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
|
||||
QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage());
|
||||
|
|
40
interface/src/ui/overlays/LocalModelsOverlay.cpp
Normal file
40
interface/src/ui/overlays/LocalModelsOverlay.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// LocalModelsOverlay.cpp
|
||||
// interface/src/ui/overlays
|
||||
//
|
||||
// Created by Ryan Huffman on 07/08/14.
|
||||
// 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 "Application.h"
|
||||
|
||||
#include "LocalModelsOverlay.h"
|
||||
|
||||
LocalModelsOverlay::LocalModelsOverlay(ModelTreeRenderer* modelTreeRenderer) :
|
||||
Volume3DOverlay(),
|
||||
_modelTreeRenderer(modelTreeRenderer) {
|
||||
}
|
||||
|
||||
LocalModelsOverlay::~LocalModelsOverlay() {
|
||||
}
|
||||
|
||||
void LocalModelsOverlay::update(float deltatime) {
|
||||
_modelTreeRenderer->update();
|
||||
}
|
||||
|
||||
void LocalModelsOverlay::render() {
|
||||
if (_visible) {
|
||||
glPushMatrix(); {
|
||||
Application* app = Application::getInstance();
|
||||
glm::vec3 oldTranslation = app->getViewMatrixTranslation();
|
||||
app->setViewMatrixTranslation(oldTranslation + _position);
|
||||
|
||||
_modelTreeRenderer->render();
|
||||
|
||||
Application::getInstance()->setViewMatrixTranslation(oldTranslation);
|
||||
} glPopMatrix();
|
||||
}
|
||||
}
|
32
interface/src/ui/overlays/LocalModelsOverlay.h
Normal file
32
interface/src/ui/overlays/LocalModelsOverlay.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// LocalModelsOverlay.h
|
||||
// interface/src/ui/overlays
|
||||
//
|
||||
// Created by Ryan Huffman on 07/08/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_LocalModelsOverlay_h
|
||||
#define hifi_LocalModelsOverlay_h
|
||||
|
||||
#include "models/ModelTreeRenderer.h"
|
||||
|
||||
#include "Volume3DOverlay.h"
|
||||
|
||||
class LocalModelsOverlay : public Volume3DOverlay {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalModelsOverlay(ModelTreeRenderer* modelTreeRenderer);
|
||||
~LocalModelsOverlay();
|
||||
|
||||
virtual void update(float deltatime);
|
||||
virtual void render();
|
||||
|
||||
private:
|
||||
ModelTreeRenderer *_modelTreeRenderer;
|
||||
};
|
||||
|
||||
#endif // hifi_LocalModelsOverlay_h
|
|
@ -14,6 +14,7 @@
|
|||
#include "Cube3DOverlay.h"
|
||||
#include "ImageOverlay.h"
|
||||
#include "Line3DOverlay.h"
|
||||
#include "LocalModelsOverlay.h"
|
||||
#include "LocalVoxelsOverlay.h"
|
||||
#include "ModelOverlay.h"
|
||||
#include "Overlays.h"
|
||||
|
@ -158,6 +159,12 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
|
|||
thisOverlay->setProperties(properties);
|
||||
created = true;
|
||||
is3D = true;
|
||||
} else if (type == "localmodels") {
|
||||
thisOverlay = new LocalModelsOverlay(Application::getInstance()->getModelClipboardRenderer());
|
||||
thisOverlay->init(_parent);
|
||||
thisOverlay->setProperties(properties);
|
||||
created = true;
|
||||
is3D = true;
|
||||
} else if (type == "model") {
|
||||
thisOverlay = new ModelOverlay();
|
||||
thisOverlay->init(_parent);
|
||||
|
|
|
@ -212,7 +212,7 @@ SequenceNumberStats::ArrivalInfo InboundAudioStream::frameReceivedUpdateNetworkS
|
|||
// discard the first few packets we receive since they usually have gaps that aren't represensative of normal jitter
|
||||
const int NUM_INITIAL_PACKETS_DISCARD = 3;
|
||||
quint64 now = usecTimestampNow();
|
||||
if (_incomingSequenceNumberStats.getNumReceived() > NUM_INITIAL_PACKETS_DISCARD) {
|
||||
if (_incomingSequenceNumberStats.getReceived() > NUM_INITIAL_PACKETS_DISCARD) {
|
||||
quint64 gap = now - _lastFrameReceivedTime;
|
||||
_interframeTimeGapStatsForStatsPacket.update(gap);
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ public:
|
|||
int getSilentFramesDropped() const { return _silentFramesDropped; }
|
||||
int getOverflowCount() const { return _ringBuffer.getOverflowCount(); }
|
||||
|
||||
int getPacketReceived() const { return _incomingSequenceNumberStats.getNumReceived(); }
|
||||
int getPacketsReceived() const { return _incomingSequenceNumberStats.getReceived(); }
|
||||
|
||||
private:
|
||||
void starved();
|
||||
|
|
|
@ -1157,6 +1157,38 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
|
|||
_defaultSettings = false;
|
||||
}
|
||||
|
||||
void ModelItemProperties::copyFromNewModelItem(const ModelItem& modelItem) {
|
||||
_position = modelItem.getPosition() * (float) TREE_SCALE;
|
||||
_color = modelItem.getXColor();
|
||||
_radius = modelItem.getRadius() * (float) TREE_SCALE;
|
||||
_shouldDie = modelItem.getShouldDie();
|
||||
_modelURL = modelItem.getModelURL();
|
||||
_modelRotation = modelItem.getModelRotation();
|
||||
_animationURL = modelItem.getAnimationURL();
|
||||
_animationIsPlaying = modelItem.getAnimationIsPlaying();
|
||||
_animationFrameIndex = modelItem.getAnimationFrameIndex();
|
||||
_animationFPS = modelItem.getAnimationFPS();
|
||||
_glowLevel = modelItem.getGlowLevel();
|
||||
_sittingPoints = modelItem.getSittingPoints();
|
||||
|
||||
_id = modelItem.getID();
|
||||
_idSet = true;
|
||||
|
||||
_positionChanged = true;
|
||||
_colorChanged = true;
|
||||
_radiusChanged = true;
|
||||
|
||||
_shouldDieChanged = true;
|
||||
_modelURLChanged = true;
|
||||
_modelRotationChanged = true;
|
||||
_animationURLChanged = true;
|
||||
_animationIsPlayingChanged = true;
|
||||
_animationFrameIndexChanged = true;
|
||||
_animationFPSChanged = true;
|
||||
_glowLevelChanged = true;
|
||||
_defaultSettings = true;
|
||||
}
|
||||
|
||||
QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties) {
|
||||
return properties.copyToScriptValue(engine);
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ public:
|
|||
|
||||
void copyToModelItem(ModelItem& modelItem) const;
|
||||
void copyFromModelItem(const ModelItem& modelItem);
|
||||
void copyFromNewModelItem(const ModelItem& modelItem);
|
||||
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
xColor getColor() const { return _color; }
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ModelEditPacketSender.h"
|
||||
#include "ModelItem.h"
|
||||
|
||||
#include "ModelTree.h"
|
||||
|
||||
ModelTree::ModelTree(bool shouldReaverage) : Octree(shouldReaverage) {
|
||||
|
@ -108,6 +111,7 @@ bool FindAndUpdateModelOperator::PostRecursion(OctreeElement* element) {
|
|||
return !_found; // if we haven't yet found it, keep looking
|
||||
}
|
||||
|
||||
|
||||
// TODO: improve this to not use multiple recursions
|
||||
void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) {
|
||||
// First, look for the existing model in the tree..
|
||||
|
@ -199,6 +203,32 @@ void ModelTree::deleteModel(const ModelItemID& modelID) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModelTree::sendModels(ModelEditPacketSender* packetSender, float x, float y, float z) {
|
||||
SendModelsOperationArgs args;
|
||||
args.packetSender = packetSender;
|
||||
args.root = glm::vec3(x, y, z);
|
||||
recurseTreeWithOperation(sendModelsOperation, &args);
|
||||
packetSender->releaseQueuedMessages();
|
||||
}
|
||||
|
||||
bool ModelTree::sendModelsOperation(OctreeElement* element, void* extraData) {
|
||||
SendModelsOperationArgs* args = static_cast<SendModelsOperationArgs*>(extraData);
|
||||
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
|
||||
|
||||
const QList<ModelItem>& modelList = modelTreeElement->getModels();
|
||||
|
||||
for (int i = 0; i < modelList.size(); i++) {
|
||||
uint32_t creatorTokenID = ModelItem::getNextCreatorTokenID();
|
||||
ModelItemID id(NEW_MODEL, creatorTokenID, false);
|
||||
ModelItemProperties properties;
|
||||
properties.copyFromNewModelItem(modelList.at(i));
|
||||
properties.setPosition(properties.getPosition() + args->root);
|
||||
args->packetSender->queueModelEditMessage(PacketTypeModelAddOrEdit, id, properties);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// scans the tree and handles mapping locally created models to know IDs.
|
||||
// in the event that this tree is also viewing the scene, then we need to also
|
||||
// search the tree to make sure we don't have a duplicate model from the viewing
|
||||
|
@ -353,8 +383,28 @@ public:
|
|||
QVector<ModelItem*> _foundModels;
|
||||
};
|
||||
|
||||
void ModelTree::findModelsInCube(const AACube& cube, QVector<ModelItem*>& foundModels) {
|
||||
FindModelsInCubeArgs args(cube);
|
||||
lockForRead();
|
||||
recurseTreeWithOperation(findInCubeOperation, &args);
|
||||
unlock();
|
||||
// swap the two lists of model pointers instead of copy
|
||||
foundModels.swap(args._foundModels);
|
||||
}
|
||||
|
||||
bool ModelTree::findInCubeOperation(OctreeElement* element, void* extraData) {
|
||||
FindModelsInCubeArgs* args = static_cast<FindModelsInCubeArgs*>(extraData);
|
||||
const AACube& elementCube = element->getAACube();
|
||||
if (elementCube.touches(args->_cube)) {
|
||||
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
|
||||
modelTreeElement->getModelsInside(args->_cube, args->_foundModels);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelTree::findInCubeForUpdateOperation(OctreeElement* element, void* extraData) {
|
||||
FindModelsInCubeArgs* args = static_cast< FindModelsInCubeArgs*>(extraData);
|
||||
FindModelsInCubeArgs* args = static_cast<FindModelsInCubeArgs*>(extraData);
|
||||
const AACube& elementCube = element->getAACube();
|
||||
if (elementCube.touches(args->_cube)) {
|
||||
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
|
||||
|
@ -364,7 +414,7 @@ bool ModelTree::findInCubeForUpdateOperation(OctreeElement* element, void* extra
|
|||
return false;
|
||||
}
|
||||
|
||||
void ModelTree::findModelsForUpdate(const AACube& cube, QVector<ModelItem*> foundModels) {
|
||||
void ModelTree::findModelsForUpdate(const AACube& cube, QVector<ModelItem*>& foundModels) {
|
||||
FindModelsInCubeArgs args(cube);
|
||||
lockForRead();
|
||||
recurseTreeWithOperation(findInCubeForUpdateOperation, &args);
|
||||
|
|
|
@ -39,7 +39,6 @@ public:
|
|||
/// Type safe version of getRoot()
|
||||
ModelTreeElement* getRoot() { return static_cast<ModelTreeElement*>(_rootElement); }
|
||||
|
||||
|
||||
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
|
||||
// own definition. Implement these to allow your octree based server to support editing
|
||||
virtual bool getWantSVOfileVersions() const { return true; }
|
||||
|
@ -65,12 +64,13 @@ public:
|
|||
/// \param foundModels[out] vector of const ModelItem*
|
||||
/// \remark Side effect: any initial contents in foundModels will be lost
|
||||
void findModels(const glm::vec3& center, float radius, QVector<const ModelItem*>& foundModels);
|
||||
void findModelsInCube(const AACube& cube, QVector<ModelItem*>& foundModels);
|
||||
|
||||
/// finds all models that touch a cube
|
||||
/// \param cube the query cube
|
||||
/// \param foundModels[out] vector of non-const ModelItem*
|
||||
/// \remark Side effect: any initial contents in models will be lost
|
||||
void findModelsForUpdate(const AACube& cube, QVector<ModelItem*> foundModels);
|
||||
void findModelsForUpdate(const AACube& cube, QVector<ModelItem*>& foundModels);
|
||||
|
||||
void addNewlyCreatedHook(NewlyCreatedModelHook* hook);
|
||||
void removeNewlyCreatedHook(NewlyCreatedModelHook* hook);
|
||||
|
@ -87,11 +87,15 @@ public:
|
|||
void setFBXService(ModelItemFBXService* service) { _fbxService = service; }
|
||||
const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) {
|
||||
return _fbxService ? _fbxService->getGeometryForModel(modelItem) : NULL;
|
||||
|
||||
}
|
||||
void sendModels(ModelEditPacketSender* packetSender, float x, float y, float z);
|
||||
|
||||
private:
|
||||
|
||||
static bool sendModelsOperation(OctreeElement* element, void* extraData);
|
||||
static bool updateOperation(OctreeElement* element, void* extraData);
|
||||
static bool findInCubeOperation(OctreeElement* element, void* extraData);
|
||||
static bool findAndUpdateOperation(OctreeElement* element, void* extraData);
|
||||
static bool findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData);
|
||||
static bool findNearPointOperation(OctreeElement* element, void* extraData);
|
||||
|
|
|
@ -399,6 +399,19 @@ void ModelTreeElement::getModels(const glm::vec3& searchPosition, float searchRa
|
|||
}
|
||||
}
|
||||
|
||||
void ModelTreeElement::getModelsInside(const AACube& box, QVector<ModelItem*>& foundModels) {
|
||||
QList<ModelItem>::iterator modelItr = _modelItems->begin();
|
||||
QList<ModelItem>::iterator modelEnd = _modelItems->end();
|
||||
AACube modelCube;
|
||||
while(modelItr != modelEnd) {
|
||||
ModelItem* model = &(*modelItr);
|
||||
if (box.contains(model->getPosition())) {
|
||||
foundModels.push_back(model);
|
||||
}
|
||||
++modelItr;
|
||||
}
|
||||
}
|
||||
|
||||
void ModelTreeElement::getModelsForUpdate(const AACube& box, QVector<ModelItem*>& foundModels) {
|
||||
QList<ModelItem>::iterator modelItr = _modelItems->begin();
|
||||
QList<ModelItem>::iterator modelEnd = _modelItems->end();
|
||||
|
|
|
@ -44,6 +44,12 @@ public:
|
|||
bool isViewing;
|
||||
};
|
||||
|
||||
class SendModelsOperationArgs {
|
||||
public:
|
||||
glm::vec3 root;
|
||||
ModelEditPacketSender* packetSender;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class ModelTreeElement : public OctreeElement {
|
||||
|
@ -132,6 +138,8 @@ public:
|
|||
/// \param models[out] vector of non-const ModelItem*
|
||||
void getModelsForUpdate(const AACube& box, QVector<ModelItem*>& foundModels);
|
||||
|
||||
void getModelsInside(const AACube& box, QVector<ModelItem*>& foundModels);
|
||||
|
||||
const ModelItem* getModelWithID(uint32_t id) const;
|
||||
|
||||
bool removeModelWithID(uint32_t id);
|
||||
|
|
|
@ -40,6 +40,15 @@ void ResourceCache::refresh(const QUrl& url) {
|
|||
}
|
||||
|
||||
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QSharedPointer<Resource> result;
|
||||
QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QSharedPointer<Resource>, result), Q_ARG(const QUrl&, url), Q_ARG(const QUrl&, fallback),
|
||||
Q_ARG(bool, delayLoad), Q_ARG(void*, extra));
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
|
||||
return getResource(fallback, QUrl(), delayLoad);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ protected:
|
|||
/// \param fallback a fallback URL to load if the desired one is unavailable
|
||||
/// \param delayLoad if true, don't load the resource immediately; wait until load is first requested
|
||||
/// \param extra extra data to pass to the creator, if appropriate
|
||||
QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(),
|
||||
Q_INVOKABLE QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(),
|
||||
bool delayLoad = false, void* extra = NULL);
|
||||
|
||||
/// Creates a new resource.
|
||||
|
|
|
@ -13,19 +13,71 @@
|
|||
|
||||
#include <limits>
|
||||
|
||||
SequenceNumberStats::SequenceNumberStats(int statsHistoryLength)
|
||||
: _lastReceived(std::numeric_limits<quint16>::max()),
|
||||
SequenceNumberStats::SequenceNumberStats(int statsHistoryLength, bool canDetectOutOfSync)
|
||||
: _received(0),
|
||||
_lastReceivedSequence(0),
|
||||
_missingSet(),
|
||||
_stats(),
|
||||
_lastSenderUUID(),
|
||||
_statsHistory(statsHistoryLength)
|
||||
_statsHistory(statsHistoryLength),
|
||||
_canHaveChild(canDetectOutOfSync),
|
||||
_childInstance(NULL),
|
||||
_consecutiveReasonable(0)
|
||||
{
|
||||
}
|
||||
|
||||
SequenceNumberStats::SequenceNumberStats(const SequenceNumberStats& other)
|
||||
: _received(other._received),
|
||||
_lastReceivedSequence(other._lastReceivedSequence),
|
||||
_missingSet(other._missingSet),
|
||||
_stats(other._stats),
|
||||
_lastSenderUUID(other._lastSenderUUID),
|
||||
_statsHistory(other._statsHistory),
|
||||
_childInstance(NULL),
|
||||
_canHaveChild(other._canHaveChild),
|
||||
_consecutiveReasonable(other._consecutiveReasonable)
|
||||
{
|
||||
if (other._childInstance) {
|
||||
_childInstance = new SequenceNumberStats(*other._childInstance);
|
||||
}
|
||||
}
|
||||
|
||||
SequenceNumberStats& SequenceNumberStats::operator=(const SequenceNumberStats& rhs) {
|
||||
_received = rhs._received;
|
||||
_lastReceivedSequence = rhs._lastReceivedSequence;
|
||||
_missingSet = rhs._missingSet;
|
||||
_stats = rhs._stats;
|
||||
_lastSenderUUID = rhs._lastSenderUUID;
|
||||
_statsHistory = rhs._statsHistory;
|
||||
_canHaveChild = rhs._canHaveChild;
|
||||
_consecutiveReasonable = rhs._consecutiveReasonable;
|
||||
|
||||
if (rhs._childInstance) {
|
||||
_childInstance = new SequenceNumberStats(*rhs._childInstance);
|
||||
} else {
|
||||
_childInstance = NULL;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SequenceNumberStats::~SequenceNumberStats() {
|
||||
if (_childInstance) {
|
||||
delete _childInstance;
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceNumberStats::reset() {
|
||||
_received = 0;
|
||||
_missingSet.clear();
|
||||
_stats = PacketStreamStats();
|
||||
_lastSenderUUID = QUuid();
|
||||
_statsHistory.clear();
|
||||
|
||||
if (_childInstance) {
|
||||
delete _childInstance;
|
||||
_childInstance = NULL;
|
||||
}
|
||||
_consecutiveReasonable = 0;
|
||||
}
|
||||
|
||||
static const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
|
||||
|
@ -36,7 +88,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
|
|||
|
||||
// if the sender node has changed, reset all stats
|
||||
if (senderUUID != _lastSenderUUID) {
|
||||
if (_stats._numReceived > 0) {
|
||||
if (_received > 0) {
|
||||
qDebug() << "sequence number stats was reset due to new sender node";
|
||||
qDebug() << "previous:" << _lastSenderUUID << "current:" << senderUUID;
|
||||
reset();
|
||||
|
@ -45,13 +97,14 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
|
|||
}
|
||||
|
||||
// determine our expected sequence number... handle rollover appropriately
|
||||
quint16 expected = _stats._numReceived > 0 ? _lastReceived + (quint16)1 : incoming;
|
||||
quint16 expected = _received > 0 ? _lastReceivedSequence + (quint16)1 : incoming;
|
||||
|
||||
_stats._numReceived++;
|
||||
_received++;
|
||||
|
||||
if (incoming == expected) { // on time
|
||||
arrivalInfo._status = OnTime;
|
||||
_lastReceived = incoming;
|
||||
_lastReceivedSequence = incoming;
|
||||
_stats._expectedReceived++;
|
||||
} else { // out of order
|
||||
|
||||
if (wantExtraDebugging) {
|
||||
|
@ -74,14 +127,76 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
|
|||
} else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
|
||||
arrivalInfo._status = Unreasonable;
|
||||
|
||||
// ignore packet if gap is unreasonable
|
||||
qDebug() << "ignoring unreasonable sequence number:" << incoming
|
||||
<< "previous:" << _lastReceived;
|
||||
_stats._numUnreasonable++;
|
||||
qDebug() << "unreasonable sequence number:" << incoming << "previous:" << _lastReceivedSequence;
|
||||
|
||||
_stats._unreasonable++;
|
||||
_consecutiveReasonable = 0;
|
||||
|
||||
|
||||
// if _canHaveChild, create a child instance of SequenceNumberStats to track this unreasonable seq num and ones in the future.
|
||||
// if the child instance detects a valid stream of seq nums up to some length, the seq nums sender probably
|
||||
// fell out of sync with us.
|
||||
|
||||
if (_canHaveChild) {
|
||||
|
||||
if (!_childInstance) {
|
||||
_childInstance = new SequenceNumberStats(0, false);
|
||||
}
|
||||
|
||||
ArrivalInfo unreasonableTrackerArrivalInfo = _childInstance->sequenceNumberReceived(incoming);
|
||||
|
||||
// the child instance will be used to detect some threshold number seq nums in a row that are perfectly
|
||||
// in order.
|
||||
|
||||
const int UNREASONABLE_TRACKER_RECEIVED_THRESHOLD = 8;
|
||||
|
||||
if (unreasonableTrackerArrivalInfo._status != OnTime) {
|
||||
_childInstance->reset();
|
||||
|
||||
} else if (_childInstance->getReceived() >= UNREASONABLE_TRACKER_RECEIVED_THRESHOLD) {
|
||||
|
||||
// the child instance has detected a threshold number of consecutive seq nums.
|
||||
// copy its state to this instance.
|
||||
|
||||
_received = _childInstance->_received;
|
||||
_lastReceivedSequence = _childInstance->_lastReceivedSequence;
|
||||
_missingSet = _childInstance->_missingSet;
|
||||
_stats = _childInstance->_stats;
|
||||
|
||||
// don't copy _lastSenderUUID; _unreasonableTracker always has null UUID for that member.
|
||||
// ours should be up-to-date.
|
||||
|
||||
// don't copy _statsHistory; _unreasonableTracker keeps a history of length 0.
|
||||
// simply clear ours.
|
||||
_statsHistory.clear();
|
||||
|
||||
arrivalInfo = unreasonableTrackerArrivalInfo;
|
||||
|
||||
// delete child instance;
|
||||
delete _childInstance;
|
||||
_childInstance = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return arrivalInfo;
|
||||
}
|
||||
|
||||
_consecutiveReasonable++;
|
||||
|
||||
// if we got a reasonable seq num but have a child instance tracking unreasonable seq nums,
|
||||
// reset it. if many consecutive reasonable seq nums have occurred (implying the unreasonable seq num
|
||||
// that caused the creation of the child instance was probably a fluke), delete our child instance.
|
||||
if (_childInstance) {
|
||||
const int CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD = 8;
|
||||
if (_consecutiveReasonable >= CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD) {
|
||||
_childInstance->reset();
|
||||
} else {
|
||||
delete _childInstance;
|
||||
_childInstance = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now that rollover has been corrected for (if it occurred), incomingInt and expectedInt can be
|
||||
// compared to each other directly, though one of them might be negative
|
||||
|
||||
|
@ -94,10 +209,11 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
|
|||
qDebug() << "this packet is earlier than expected...";
|
||||
qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt);
|
||||
}
|
||||
|
||||
_stats._numEarly++;
|
||||
_stats._numLost += (incomingInt - expectedInt);
|
||||
_lastReceived = incoming;
|
||||
int skipped = incomingInt - expectedInt;
|
||||
_stats._early++;
|
||||
_stats._lost += skipped;
|
||||
_stats._expectedReceived += (skipped + 1);
|
||||
_lastReceivedSequence = incoming;
|
||||
|
||||
// add all sequence numbers that were skipped to the missing sequence numbers list
|
||||
for (int missingInt = expectedInt; missingInt < incomingInt; missingInt++) {
|
||||
|
@ -114,7 +230,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
|
|||
qDebug() << "this packet is later than expected...";
|
||||
}
|
||||
|
||||
_stats._numLate++;
|
||||
_stats._late++;
|
||||
|
||||
// do not update _lastReceived; it shouldn't become smaller
|
||||
|
||||
|
@ -125,18 +241,19 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
|
|||
if (wantExtraDebugging) {
|
||||
qDebug() << "found it in _missingSet";
|
||||
}
|
||||
_stats._numLost--;
|
||||
_stats._numRecovered++;
|
||||
_stats._lost--;
|
||||
_stats._recovered++;
|
||||
} else {
|
||||
arrivalInfo._status = Duplicate;
|
||||
|
||||
if (wantExtraDebugging) {
|
||||
qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate";
|
||||
}
|
||||
_stats._numDuplicate++;
|
||||
_stats._duplicate++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arrivalInfo;
|
||||
}
|
||||
|
||||
|
@ -148,7 +265,7 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
|
|||
// some older sequence numbers may be from before a rollover point; this must be handled.
|
||||
// some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received
|
||||
// before the most recent rollover.
|
||||
int cutoff = (int)_lastReceived - MAX_REASONABLE_SEQUENCE_GAP;
|
||||
int cutoff = (int)_lastReceivedSequence - MAX_REASONABLE_SEQUENCE_GAP;
|
||||
if (cutoff >= 0) {
|
||||
quint16 nonRolloverCutoff = (quint16)cutoff;
|
||||
QSet<quint16>::iterator i = _missingSet.begin();
|
||||
|
@ -159,7 +276,7 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
|
|||
qDebug() << "old age cutoff:" << nonRolloverCutoff;
|
||||
}
|
||||
|
||||
if (missing > _lastReceived || missing < nonRolloverCutoff) {
|
||||
if (missing > _lastReceivedSequence || missing < nonRolloverCutoff) {
|
||||
i = _missingSet.erase(i);
|
||||
if (wantExtraDebugging) {
|
||||
qDebug() << "pruning really old missing sequence:" << missing;
|
||||
|
@ -178,7 +295,7 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
|
|||
qDebug() << "old age cutoff:" << rolloverCutoff;
|
||||
}
|
||||
|
||||
if (missing > _lastReceived && missing < rolloverCutoff) {
|
||||
if (missing > _lastReceivedSequence && missing < rolloverCutoff) {
|
||||
i = _missingSet.erase(i);
|
||||
if (wantExtraDebugging) {
|
||||
qDebug() << "pruning really old missing sequence:" << missing;
|
||||
|
@ -202,13 +319,13 @@ PacketStreamStats SequenceNumberStats::getStatsForHistoryWindow() const {
|
|||
|
||||
// calculate difference between newest stats and oldest stats to get window stats
|
||||
PacketStreamStats windowStats;
|
||||
windowStats._numReceived = newestStats->_numReceived - oldestStats->_numReceived;
|
||||
windowStats._numUnreasonable = newestStats->_numUnreasonable - oldestStats->_numUnreasonable;
|
||||
windowStats._numEarly = newestStats->_numEarly - oldestStats->_numEarly;
|
||||
windowStats._numLate = newestStats->_numLate - oldestStats->_numLate;
|
||||
windowStats._numLost = newestStats->_numLost - oldestStats->_numLost;
|
||||
windowStats._numRecovered = newestStats->_numRecovered - oldestStats->_numRecovered;
|
||||
windowStats._numDuplicate = newestStats->_numDuplicate - oldestStats->_numDuplicate;
|
||||
windowStats._expectedReceived = newestStats->_expectedReceived - oldestStats->_expectedReceived;
|
||||
windowStats._unreasonable = newestStats->_unreasonable - oldestStats->_unreasonable;
|
||||
windowStats._early = newestStats->_early - oldestStats->_early;
|
||||
windowStats._late = newestStats->_late - oldestStats->_late;
|
||||
windowStats._lost = newestStats->_lost - oldestStats->_lost;
|
||||
windowStats._recovered = newestStats->_recovered - oldestStats->_recovered;
|
||||
windowStats._duplicate = newestStats->_duplicate - oldestStats->_duplicate;
|
||||
|
||||
return windowStats;
|
||||
}
|
||||
|
|
|
@ -18,32 +18,31 @@
|
|||
|
||||
const int MAX_REASONABLE_SEQUENCE_GAP = 1000;
|
||||
|
||||
|
||||
class PacketStreamStats {
|
||||
public:
|
||||
PacketStreamStats()
|
||||
: _numReceived(0),
|
||||
_numUnreasonable(0),
|
||||
_numEarly(0),
|
||||
_numLate(0),
|
||||
_numLost(0),
|
||||
_numRecovered(0),
|
||||
_numDuplicate(0)
|
||||
: _expectedReceived(0),
|
||||
_unreasonable(0),
|
||||
_early(0),
|
||||
_late(0),
|
||||
_lost(0),
|
||||
_recovered(0),
|
||||
_duplicate(0)
|
||||
{}
|
||||
|
||||
float getUnreasonableRate() const { return (float)_numUnreasonable / _numReceived; }
|
||||
float getNumEaryRate() const { return (float)_numEarly / _numReceived; }
|
||||
float getLateRate() const { return (float)_numLate / _numReceived; }
|
||||
float getLostRate() const { return (float)_numLost / _numReceived; }
|
||||
float getRecoveredRate() const { return (float)_numRecovered / _numReceived; }
|
||||
float getDuplicateRate() const { return (float)_numDuplicate / _numReceived; }
|
||||
float getOutOfOrderRate() const { return (float)(_early + _late) / _expectedReceived; }
|
||||
float getEaryRate() const { return (float)_early / _expectedReceived; }
|
||||
float getLateRate() const { return (float)_late / _expectedReceived; }
|
||||
float getLostRate() const { return (float)_lost / _expectedReceived; }
|
||||
|
||||
quint32 _numReceived;
|
||||
quint32 _numUnreasonable;
|
||||
quint32 _numEarly;
|
||||
quint32 _numLate;
|
||||
quint32 _numLost;
|
||||
quint32 _numRecovered;
|
||||
quint32 _numDuplicate;
|
||||
quint32 _expectedReceived;
|
||||
quint32 _unreasonable;
|
||||
quint32 _early;
|
||||
quint32 _late;
|
||||
quint32 _lost;
|
||||
quint32 _recovered;
|
||||
quint32 _duplicate;
|
||||
};
|
||||
|
||||
class SequenceNumberStats {
|
||||
|
@ -63,27 +62,36 @@ public:
|
|||
};
|
||||
|
||||
|
||||
SequenceNumberStats(int statsHistoryLength = 0);
|
||||
SequenceNumberStats(int statsHistoryLength = 0, bool canDetectOutOfSync = true);
|
||||
SequenceNumberStats(const SequenceNumberStats& other);
|
||||
SequenceNumberStats& operator=(const SequenceNumberStats& rhs);
|
||||
~SequenceNumberStats();
|
||||
|
||||
void reset();
|
||||
ArrivalInfo sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
|
||||
void pruneMissingSet(const bool wantExtraDebugging = false);
|
||||
void pushStatsToHistory() { _statsHistory.insert(_stats); }
|
||||
|
||||
quint32 getNumReceived() const { return _stats._numReceived; }
|
||||
quint32 getNumUnreasonable() const { return _stats._numUnreasonable; }
|
||||
quint32 getNumOutOfOrder() const { return _stats._numEarly + _stats._numLate; }
|
||||
quint32 getNumEarly() const { return _stats._numEarly; }
|
||||
quint32 getNumLate() const { return _stats._numLate; }
|
||||
quint32 getNumLost() const { return _stats._numLost; }
|
||||
quint32 getNumRecovered() const { return _stats._numRecovered; }
|
||||
quint32 getNumDuplicate() const { return _stats._numDuplicate; }
|
||||
quint32 getReceived() const { return _received; }
|
||||
float getUnreasonableRate() const { return _stats._unreasonable / _received; }
|
||||
|
||||
quint32 getExpectedReceived() const { return _stats._expectedReceived; }
|
||||
quint32 getUnreasonable() const { return _stats._unreasonable; }
|
||||
quint32 getOutOfOrder() const { return _stats._early + _stats._late; }
|
||||
quint32 getEarly() const { return _stats._early; }
|
||||
quint32 getLate() const { return _stats._late; }
|
||||
quint32 getLost() const { return _stats._lost; }
|
||||
quint32 getRecovered() const { return _stats._recovered; }
|
||||
quint32 getDuplicate() const { return _stats._duplicate; }
|
||||
|
||||
const PacketStreamStats& getStats() const { return _stats; }
|
||||
PacketStreamStats getStatsForHistoryWindow() const;
|
||||
const QSet<quint16>& getMissingSet() const { return _missingSet; }
|
||||
|
||||
private:
|
||||
quint16 _lastReceived;
|
||||
int _received;
|
||||
|
||||
quint16 _lastReceivedSequence;
|
||||
QSet<quint16> _missingSet;
|
||||
|
||||
PacketStreamStats _stats;
|
||||
|
@ -91,6 +99,14 @@ private:
|
|||
QUuid _lastSenderUUID;
|
||||
|
||||
RingBufferHistory<PacketStreamStats> _statsHistory;
|
||||
|
||||
|
||||
// to deal with the incoming seq nums going out of sync with this tracker, we'll create another instance
|
||||
// of this class when we encounter an unreasonable
|
||||
bool _canHaveChild;
|
||||
SequenceNumberStats* _childInstance;
|
||||
|
||||
int _consecutiveReasonable;
|
||||
};
|
||||
|
||||
#endif // hifi_SequenceNumberStats_h
|
||||
|
|
|
@ -156,7 +156,7 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL,
|
|||
} else {
|
||||
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
|
||||
qDebug() << "Downloading included script at" << url;
|
||||
qDebug() << "Downloading script at" << url;
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
|
@ -681,12 +681,12 @@ void ScriptEngine::include(const QString& includeFile) {
|
|||
#endif
|
||||
QFile scriptFile(fileName);
|
||||
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
qDebug() << "Loading file:" << fileName;
|
||||
qDebug() << "Including file:" << fileName;
|
||||
QTextStream in(&scriptFile);
|
||||
includeContents = in.readAll();
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << fileName;
|
||||
emit errorMessage("ERROR Loading file:" + fileName);
|
||||
qDebug() << "ERROR Including file:" << fileName;
|
||||
emit errorMessage("ERROR Including file:" + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -699,6 +699,11 @@ void ScriptEngine::include(const QString& includeFile) {
|
|||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::load(const QString& loadFile) {
|
||||
QUrl url = resolveInclude(loadFile);
|
||||
emit loadScript(url.toString());
|
||||
}
|
||||
|
||||
void ScriptEngine::nodeKilled(SharedNodePointer node) {
|
||||
_outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
|
||||
}
|
||||
|
|
|
@ -102,6 +102,7 @@ public slots:
|
|||
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void include(const QString& includeFile);
|
||||
void load(const QString& loadfile);
|
||||
void print(const QString& message);
|
||||
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
@ -115,6 +116,7 @@ signals:
|
|||
void errorMessage(const QString& message);
|
||||
void runningStateChanged();
|
||||
void evaluationFinished(QScriptValue result, bool isException);
|
||||
void loadScript(const QString& scriptName);
|
||||
|
||||
protected:
|
||||
QString _scriptContents;
|
||||
|
|
|
@ -80,11 +80,16 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale,
|
|||
DeleteVoxelCommand* deleteCommand = new DeleteVoxelCommand(_tree,
|
||||
addVoxelDetail,
|
||||
getVoxelPacketSender());
|
||||
_undoStackMutex.lock();
|
||||
|
||||
_undoStack->beginMacro(addCommand->text());
|
||||
// As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves.
|
||||
_undoStack->push(deleteCommand);
|
||||
_undoStack->push(addCommand);
|
||||
_undoStack->endMacro();
|
||||
|
||||
//Unlock the mutex
|
||||
_undoStackMutex.unlock();
|
||||
} else {
|
||||
// queue the destructive add
|
||||
queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail);
|
||||
|
@ -109,11 +114,15 @@ void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale
|
|||
}
|
||||
|
||||
if (_undoStack) {
|
||||
|
||||
DeleteVoxelCommand* command = new DeleteVoxelCommand(_tree,
|
||||
deleteVoxelDetail,
|
||||
getVoxelPacketSender());
|
||||
|
||||
_undoStackMutex.lock();
|
||||
// As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves.
|
||||
_undoStack->push(command);
|
||||
_undoStackMutex.unlock();
|
||||
} else {
|
||||
getVoxelPacketSender()->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &deleteVoxelDetail);
|
||||
_tree->deleteVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s);
|
||||
|
|
|
@ -102,6 +102,7 @@ private:
|
|||
void queueVoxelAdd(PacketType addPacketType, VoxelDetail& addVoxelDetails);
|
||||
VoxelTree* _tree;
|
||||
QUndoStack* _undoStack;
|
||||
QMutex _undoStackMutex;
|
||||
};
|
||||
|
||||
#endif // hifi_VoxelsScriptingInterface_h
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
#include <MovingMinMaxAvg.h> // for MovingMinMaxAvg
|
||||
#include <SharedUtil.h> // for usecTimestampNow
|
||||
|
||||
void runSend(const char* addressOption, int port, int gap, int size);
|
||||
void runReceive(const char* addressOption, int port, int gap, int size);
|
||||
const quint64 MSEC_TO_USEC = 1000;
|
||||
|
||||
void runSend(const char* addressOption, int port, int gap, int size, int report);
|
||||
void runReceive(const char* addressOption, int port, int gap, int size, int report);
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
if (argc != 6) {
|
||||
printf("usage: jitter-tests <--send|--receive> <address> <port> <gap in ms> <packet size>\n");
|
||||
if (argc != 7) {
|
||||
printf("usage: jitter-tests <--send|--receive> <address> <port> <gap in usecs> <packet size> <report interval in msecs>\n");
|
||||
exit(1);
|
||||
}
|
||||
const char* typeOption = argv[1];
|
||||
|
@ -31,9 +33,11 @@ int main(int argc, const char * argv[]) {
|
|||
const char* portOption = argv[3];
|
||||
const char* gapOption = argv[4];
|
||||
const char* sizeOption = argv[5];
|
||||
const char* reportOption = argv[6];
|
||||
int port = atoi(portOption);
|
||||
int gap = atoi(gapOption);
|
||||
int size = atoi(sizeOption);
|
||||
int report = atoi(reportOption);
|
||||
|
||||
std::cout << "type:" << typeOption << "\n";
|
||||
std::cout << "address:" << addressOption << "\n";
|
||||
|
@ -42,14 +46,14 @@ int main(int argc, const char * argv[]) {
|
|||
std::cout << "size:" << size << "\n";
|
||||
|
||||
if (strcmp(typeOption, "--send") == 0) {
|
||||
runSend(addressOption, port, gap, size);
|
||||
runSend(addressOption, port, gap, size, report);
|
||||
} else if (strcmp(typeOption, "--receive") == 0) {
|
||||
runReceive(addressOption, port, gap, size);
|
||||
runReceive(addressOption, port, gap, size, report);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void runSend(const char* addressOption, int port, int gap, int size) {
|
||||
void runSend(const char* addressOption, int port, int gap, int size, int report) {
|
||||
std::cout << "runSend...\n";
|
||||
|
||||
int sockfd;
|
||||
|
@ -66,9 +70,13 @@ void runSend(const char* addressOption, int port, int gap, int size) {
|
|||
servaddr.sin_port=htons(port);
|
||||
|
||||
const int SAMPLES_FOR_30_SECONDS = 30 * 1000000 / gap;
|
||||
|
||||
std::cout << "SAMPLES_FOR_30_SECONDS:" << SAMPLES_FOR_30_SECONDS << "\n";
|
||||
|
||||
MovingMinMaxAvg<int> timeGaps(1, SAMPLES_FOR_30_SECONDS); // stats
|
||||
|
||||
quint64 last = usecTimestampNow();
|
||||
quint64 lastReport = 0;
|
||||
|
||||
while (true) {
|
||||
|
||||
|
@ -81,21 +89,24 @@ void runSend(const char* addressOption, int port, int gap, int size) {
|
|||
|
||||
int gapDifferece = actualGap - gap;
|
||||
timeGaps.update(gapDifferece);
|
||||
std::cout << "packet sent gap: " << actualGap << " "
|
||||
<< "gapDifference: " << gapDifferece << " "
|
||||
<< "min: " << timeGaps.getMin() << " "
|
||||
<< "max: " << timeGaps.getMax() << " "
|
||||
<< "avg: " << timeGaps.getAverage() << " "
|
||||
<< "min last 30: " << timeGaps.getWindowMin() << " "
|
||||
<< "max last 30: " << timeGaps.getWindowMax() << " "
|
||||
<< "avg last 30: " << timeGaps.getWindowAverage() << " "
|
||||
<< "\n";
|
||||
last = now;
|
||||
|
||||
if (now - lastReport >= (report * MSEC_TO_USEC)) {
|
||||
std::cout << "SEND gap Difference From Expected "
|
||||
<< "min: " << timeGaps.getMin() << " usecs, "
|
||||
<< "max: " << timeGaps.getMax() << " usecs, "
|
||||
<< "avg: " << timeGaps.getAverage() << " usecs, "
|
||||
<< "min last 30: " << timeGaps.getWindowMin() << " usecs, "
|
||||
<< "max last 30: " << timeGaps.getWindowMax() << " usecs, "
|
||||
<< "avg last 30: " << timeGaps.getWindowAverage() << " usecs "
|
||||
<< "\n";
|
||||
lastReport = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runReceive(const char* addressOption, int port, int gap, int size) {
|
||||
void runReceive(const char* addressOption, int port, int gap, int size, int report) {
|
||||
std::cout << "runReceive...\n";
|
||||
|
||||
|
||||
|
@ -113,6 +124,9 @@ void runReceive(const char* addressOption, int port, int gap, int size) {
|
|||
myaddr.sin_port=htons(port);
|
||||
|
||||
const int SAMPLES_FOR_30_SECONDS = 30 * 1000000 / gap;
|
||||
|
||||
std::cout << "SAMPLES_FOR_30_SECONDS:" << SAMPLES_FOR_30_SECONDS << "\n";
|
||||
|
||||
MovingMinMaxAvg<int> timeGaps(1, SAMPLES_FOR_30_SECONDS); // stats
|
||||
|
||||
if (bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
|
||||
|
@ -121,6 +135,7 @@ void runReceive(const char* addressOption, int port, int gap, int size) {
|
|||
}
|
||||
|
||||
quint64 last = 0; // first case
|
||||
quint64 lastReport = 0;
|
||||
|
||||
while (true) {
|
||||
n = recvfrom(sockfd, inputBuffer, size, 0, NULL, NULL); // we don't care about where it came from
|
||||
|
@ -133,16 +148,19 @@ void runReceive(const char* addressOption, int port, int gap, int size) {
|
|||
int actualGap = now - last;
|
||||
int gapDifferece = actualGap - gap;
|
||||
timeGaps.update(gapDifferece);
|
||||
std::cout << "packet received gap:" << actualGap << " "
|
||||
<< "gapDifference: " << gapDifferece << " "
|
||||
<< "min: " << timeGaps.getMin() << " "
|
||||
<< "max: " << timeGaps.getMax() << " "
|
||||
<< "avg: " << timeGaps.getAverage() << " "
|
||||
<< "min last 30: " << timeGaps.getWindowMin() << " "
|
||||
<< "max last 30: " << timeGaps.getWindowMax() << " "
|
||||
<< "avg last 30: " << timeGaps.getWindowAverage() << " "
|
||||
<< "\n";
|
||||
last = now;
|
||||
|
||||
if (now - lastReport >= (report * MSEC_TO_USEC)) {
|
||||
std::cout << "RECEIVE gap Difference From Expected "
|
||||
<< "min: " << timeGaps.getMin() << " usecs, "
|
||||
<< "max: " << timeGaps.getMax() << " usecs, "
|
||||
<< "avg: " << timeGaps.getAverage() << " usecs, "
|
||||
<< "min last 30: " << timeGaps.getWindowMin() << " usecs, "
|
||||
<< "max last 30: " << timeGaps.getWindowMax() << " usecs, "
|
||||
<< "avg last 30: " << timeGaps.getWindowAverage() << " usecs "
|
||||
<< "\n";
|
||||
lastReport = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
|
||||
|
||||
void SequenceNumberStatsTests::runAllTests() {
|
||||
|
||||
rolloverTest();
|
||||
earlyLateTest();
|
||||
duplicateTest();
|
||||
pruneTest();
|
||||
recursiveTest();
|
||||
}
|
||||
|
||||
const quint32 UINT16_RANGE = std::numeric_limits<quint16>::max() + 1;
|
||||
|
@ -38,12 +38,12 @@ void SequenceNumberStatsTests::rolloverTest() {
|
|||
stats.sequenceNumberReceived(seq);
|
||||
seq = seq + (quint16)1;
|
||||
|
||||
assert(stats.getNumDuplicate() == 0);
|
||||
assert(stats.getNumEarly() == 0);
|
||||
assert(stats.getNumLate() == 0);
|
||||
assert(stats.getNumLost() == 0);
|
||||
assert(stats.getNumReceived() == i + 1);
|
||||
assert(stats.getNumRecovered() == 0);
|
||||
assert(stats.getDuplicate() == 0);
|
||||
assert(stats.getEarly() == 0);
|
||||
assert(stats.getLate() == 0);
|
||||
assert(stats.getLost() == 0);
|
||||
assert(stats.getReceived() == i + 1);
|
||||
assert(stats.getRecovered() == 0);
|
||||
}
|
||||
stats.reset();
|
||||
}
|
||||
|
@ -69,12 +69,12 @@ void SequenceNumberStatsTests::earlyLateTest() {
|
|||
seq = seq + (quint16)1;
|
||||
numSent++;
|
||||
|
||||
assert(stats.getNumDuplicate() == 0);
|
||||
assert(stats.getNumEarly() == numEarly);
|
||||
assert(stats.getNumLate() == numLate);
|
||||
assert(stats.getNumLost() == numLost);
|
||||
assert(stats.getNumReceived() == numSent);
|
||||
assert(stats.getNumRecovered() == numRecovered);
|
||||
assert(stats.getDuplicate() == 0);
|
||||
assert(stats.getEarly() == numEarly);
|
||||
assert(stats.getLate() == numLate);
|
||||
assert(stats.getLost() == numLost);
|
||||
assert(stats.getReceived() == numSent);
|
||||
assert(stats.getRecovered() == numRecovered);
|
||||
}
|
||||
|
||||
// skip 10
|
||||
|
@ -89,12 +89,12 @@ void SequenceNumberStatsTests::earlyLateTest() {
|
|||
seq = seq + (quint16)1;
|
||||
numSent++;
|
||||
|
||||
assert(stats.getNumDuplicate() == 0);
|
||||
assert(stats.getNumEarly() == numEarly);
|
||||
assert(stats.getNumLate() == numLate);
|
||||
assert(stats.getNumLost() == numLost);
|
||||
assert(stats.getNumReceived() == numSent);
|
||||
assert(stats.getNumRecovered() == numRecovered);
|
||||
assert(stats.getDuplicate() == 0);
|
||||
assert(stats.getEarly() == numEarly);
|
||||
assert(stats.getLate() == numLate);
|
||||
assert(stats.getLost() == numLost);
|
||||
assert(stats.getReceived() == numSent);
|
||||
assert(stats.getRecovered() == numRecovered);
|
||||
}
|
||||
|
||||
// send ones we skipped
|
||||
|
@ -106,12 +106,12 @@ void SequenceNumberStatsTests::earlyLateTest() {
|
|||
numLost--;
|
||||
numRecovered++;
|
||||
|
||||
assert(stats.getNumDuplicate() == 0);
|
||||
assert(stats.getNumEarly() == numEarly);
|
||||
assert(stats.getNumLate() == numLate);
|
||||
assert(stats.getNumLost() == numLost);
|
||||
assert(stats.getNumReceived() == numSent);
|
||||
assert(stats.getNumRecovered() == numRecovered);
|
||||
assert(stats.getDuplicate() == 0);
|
||||
assert(stats.getEarly() == numEarly);
|
||||
assert(stats.getLate() == numLate);
|
||||
assert(stats.getLost() == numLost);
|
||||
assert(stats.getReceived() == numSent);
|
||||
assert(stats.getRecovered() == numRecovered);
|
||||
}
|
||||
}
|
||||
stats.reset();
|
||||
|
@ -145,12 +145,12 @@ void SequenceNumberStatsTests::duplicateTest() {
|
|||
seq = seq + (quint16)1;
|
||||
numSent++;
|
||||
|
||||
assert(stats.getNumDuplicate() == numDuplicate);
|
||||
assert(stats.getNumEarly() == numEarly);
|
||||
assert(stats.getNumLate() == numLate);
|
||||
assert(stats.getNumLost() == numLost);
|
||||
assert(stats.getNumReceived() == numSent);
|
||||
assert(stats.getNumRecovered() == 0);
|
||||
assert(stats.getDuplicate() == numDuplicate);
|
||||
assert(stats.getEarly() == numEarly);
|
||||
assert(stats.getLate() == numLate);
|
||||
assert(stats.getLost() == numLost);
|
||||
assert(stats.getReceived() == numSent);
|
||||
assert(stats.getRecovered() == 0);
|
||||
}
|
||||
|
||||
// skip 10
|
||||
|
@ -167,12 +167,12 @@ void SequenceNumberStatsTests::duplicateTest() {
|
|||
seq = seq + (quint16)1;
|
||||
numSent++;
|
||||
|
||||
assert(stats.getNumDuplicate() == numDuplicate);
|
||||
assert(stats.getNumEarly() == numEarly);
|
||||
assert(stats.getNumLate() == numLate);
|
||||
assert(stats.getNumLost() == numLost);
|
||||
assert(stats.getNumReceived() == numSent);
|
||||
assert(stats.getNumRecovered() == 0);
|
||||
assert(stats.getDuplicate() == numDuplicate);
|
||||
assert(stats.getEarly() == numEarly);
|
||||
assert(stats.getLate() == numLate);
|
||||
assert(stats.getLost() == numLost);
|
||||
assert(stats.getReceived() == numSent);
|
||||
assert(stats.getRecovered() == 0);
|
||||
}
|
||||
|
||||
// send 5 duplicates from before skip
|
||||
|
@ -183,12 +183,12 @@ void SequenceNumberStatsTests::duplicateTest() {
|
|||
numDuplicate++;
|
||||
numLate++;
|
||||
|
||||
assert(stats.getNumDuplicate() == numDuplicate);
|
||||
assert(stats.getNumEarly() == numEarly);
|
||||
assert(stats.getNumLate() == numLate);
|
||||
assert(stats.getNumLost() == numLost);
|
||||
assert(stats.getNumReceived() == numSent);
|
||||
assert(stats.getNumRecovered() == 0);
|
||||
assert(stats.getDuplicate() == numDuplicate);
|
||||
assert(stats.getEarly() == numEarly);
|
||||
assert(stats.getLate() == numLate);
|
||||
assert(stats.getLost() == numLost);
|
||||
assert(stats.getReceived() == numSent);
|
||||
assert(stats.getRecovered() == 0);
|
||||
}
|
||||
|
||||
// send 5 duplicates from after skip
|
||||
|
@ -199,12 +199,12 @@ void SequenceNumberStatsTests::duplicateTest() {
|
|||
numDuplicate++;
|
||||
numLate++;
|
||||
|
||||
assert(stats.getNumDuplicate() == numDuplicate);
|
||||
assert(stats.getNumEarly() == numEarly);
|
||||
assert(stats.getNumLate() == numLate);
|
||||
assert(stats.getNumLost() == numLost);
|
||||
assert(stats.getNumReceived() == numSent);
|
||||
assert(stats.getNumRecovered() == 0);
|
||||
assert(stats.getDuplicate() == numDuplicate);
|
||||
assert(stats.getEarly() == numEarly);
|
||||
assert(stats.getLate() == numLate);
|
||||
assert(stats.getLost() == numLost);
|
||||
assert(stats.getReceived() == numSent);
|
||||
assert(stats.getRecovered() == 0);
|
||||
}
|
||||
}
|
||||
stats.reset();
|
||||
|
@ -278,3 +278,36 @@ void SequenceNumberStatsTests::pruneTest() {
|
|||
numLost = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceNumberStatsTests::recursiveTest() {
|
||||
|
||||
SequenceNumberStats stats(0);
|
||||
|
||||
quint16 sequence;
|
||||
|
||||
sequence = 89;
|
||||
stats.sequenceNumberReceived(sequence);
|
||||
|
||||
assert(stats.getUnreasonable() == 0);
|
||||
|
||||
sequence = 2990;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
stats.sequenceNumberReceived(sequence);
|
||||
sequence += (quint16)1;
|
||||
}
|
||||
|
||||
assert(stats.getUnreasonable() == 0);
|
||||
|
||||
|
||||
sequence = 0;
|
||||
for (int R = 0; R < 7; R++) {
|
||||
stats.sequenceNumberReceived(sequence);
|
||||
sequence += (quint16)2000;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
stats.sequenceNumberReceived(sequence);
|
||||
sequence += (quint16)1;
|
||||
}
|
||||
assert(stats.getUnreasonable() == 0);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace SequenceNumberStatsTests {
|
|||
void earlyLateTest();
|
||||
void duplicateTest();
|
||||
void pruneTest();
|
||||
void recursiveTest();
|
||||
};
|
||||
|
||||
#endif // hifi_SequenceNumberStatsTests_h
|
||||
|
|
Loading…
Reference in a new issue