Merge branch 'master' of https://github.com/highfidelity/hifi into sit_on_a_model

This commit is contained in:
Atlante45 2014-06-26 11:35:29 -07:00
commit 0b0cbdf340
36 changed files with 2093 additions and 503 deletions

View file

@ -13,7 +13,7 @@ if (WIN32)
elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic")
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-strict-aliasing")
endif(WIN32)
if (NOT QT_CMAKE_PREFIX_PATH)

67
examples/concertCamera.js Normal file
View file

@ -0,0 +1,67 @@
//
// concertCamera.js
//
// Created by Philip Rosedale on June 24, 2014
// Copyright 2014 High Fidelity, Inc.
//
// Move a camera through a series of pre-set locations by pressing number keys
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var oldMode;
var avatarPosition;
var cameraNumber = 0;
var freeCamera = false;
var cameraLocations = [ {x: 7972.2, y: 241.6, z: 7304.1}, {x: 7973.0, y: 241.6, z: 7304.1}, {x: 7975.5, y: 241.6, z: 7304.1}, {x: 7972.3, y: 241.6, z: 7303.3}, {x: 7971.4, y: 241.6, z: 7304.3}, {x: 7973.5, y: 240.6, z: 7302.5} ];
var cameraLookAts = [ {x: 7971.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241., z: 7304.1} ];
function saveCameraState() {
oldMode = Camera.getMode();
avatarPosition = MyAvatar.position;
Camera.setModeShiftPeriod(0.0);
Camera.setMode("independent");
}
function restoreCameraState() {
Camera.stopLooking();
Camera.setMode(oldMode);
}
function update(deltaTime) {
if (freeCamera) {
var delta = Vec3.subtract(MyAvatar.position, avatarPosition);
if (Vec3.length(delta) > 0.05) {
cameraNumber = 0;
freeCamera = false;
restoreCameraState();
}
}
}
function keyPressEvent(event) {
var choice = parseInt(event.text);
if ((choice > 0) && (choice <= cameraLocations.length)) {
print("camera " + choice);
if (!freeCamera) {
saveCameraState();
freeCamera = true;
}
Camera.setMode("independent");
Camera.setPosition(cameraLocations[choice - 1]);
Camera.keepLookingAt(cameraLookAts[choice - 1]);
}
if (event.text == "0") {
// Show camera location in log
var cameraLocation = Camera.getPosition();
print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z);
}
}
Script.update.connect(update);
Controller.keyPressEvent.connect(keyPressEvent);

View file

@ -0,0 +1,38 @@
//
// inWorldTestTone.js
//
//
// Created by Philip Rosedale on 5/29/14.
// Copyright 2014 High Fidelity, Inc.
//
// This example script plays a test tone that is useful for debugging audio dropout. 220Hz test tone played at the domain origin.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/220Sine.wav");
var soundPlaying = false;
function update(deltaTime) {
if (!Audio.isInjectorPlaying(soundPlaying)) {
var options = new AudioInjectionOptions();
options.position = { x:0, y:0, z:0 };
options.volume = 1.0;
options.loop = true;
soundPlaying = Audio.playSound(sound, options);
print("Started sound loop");
}
}
function scriptEnding() {
if (Audio.isInjectorPlaying(soundPlaying)) {
Audio.stopInjector(soundPlaying);
print("Stopped sound loop");
}
}
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

View file

@ -195,6 +195,8 @@ function keyReleaseEvent(event) {
}
}
function mousePressEvent(event) {
if (alt && !isActive) {
mouseLastX = event.x;

View file

@ -19,7 +19,7 @@ var buttonHeight = 46;
var buttonPadding = 10;
var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth;
var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 ;
var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 - (buttonHeight + buttonPadding);
var sitDownButton = Overlays.addOverlay("image", {
x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight,
@ -49,13 +49,7 @@ var pose = [
{joint:"RightFoot", rotation: {x:30, y:15.0, z:0.0}},
{joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}},
{joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}},
{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}},
{joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}},
{joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}},
{joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}}
{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}}
];
var startPoseAndTransition = [];

View file

@ -9,8 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnim.fbx";
var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnim.fbx";
var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnimPhilip.fbx";
var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnimPhilip.fbx";
var LEFT = 0;
var RIGHT = 1;

View file

@ -26,14 +26,21 @@ var RIGHT_TIP = 3;
var RIGHT_BUTTON_FWD = 11;
var RIGHT_BUTTON_3 = 9;
var BALL_RADIUS = 0.08;
var GRAVITY_STRENGTH = 0.5;
var HELD_COLOR = { red: 240, green: 0, blue: 0 };
var THROWN_COLOR = { red: 128, green: 0, blue: 0 };
var leftBallAlreadyInHand = false;
var rightBallAlreadyInHand = false;
var leftHandParticle;
var rightHandParticle;
var throwSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
var newSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
var targetRadius = 0.25;
var throwSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw");
var targetRadius = 1.0;
var wantDebugging = false;
@ -44,31 +51,19 @@ function debugPrint(message) {
}
function getBallHoldPosition(whichSide) {
var normal;
var tipPosition;
if (whichSide == LEFT_PALM) {
normal = Controller.getSpatialControlNormal(LEFT_PALM);
tipPosition = Controller.getSpatialControlPosition(LEFT_TIP);
position = MyAvatar.getLeftPalmPosition();
} else {
normal = Controller.getSpatialControlNormal(RIGHT_PALM);
tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP);
position = MyAvatar.getRightPalmPosition();
}
var BALL_FORWARD_OFFSET = 0.08; // put the ball a bit forward of fingers
position = { x: BALL_FORWARD_OFFSET * normal.x,
y: BALL_FORWARD_OFFSET * normal.y,
z: BALL_FORWARD_OFFSET * normal.z };
position.x += tipPosition.x;
position.y += tipPosition.y;
position.z += tipPosition.z;
return position;
}
function checkControllerSide(whichSide) {
var BUTTON_FWD;
var BUTTON_3;
var TRIGGER;
var palmPosition;
var ballAlreadyInHand;
var handMessage;
@ -76,18 +71,20 @@ function checkControllerSide(whichSide) {
if (whichSide == LEFT_PALM) {
BUTTON_FWD = LEFT_BUTTON_FWD;
BUTTON_3 = LEFT_BUTTON_3;
TRIGGER = 0;
palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
ballAlreadyInHand = leftBallAlreadyInHand;
handMessage = "LEFT";
} else {
BUTTON_FWD = RIGHT_BUTTON_FWD;
BUTTON_3 = RIGHT_BUTTON_3;
TRIGGER = 1;
palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
ballAlreadyInHand = rightBallAlreadyInHand;
handMessage = "RIGHT";
}
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3));
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3) || (Controller.getTriggerValue(TRIGGER) > 0.5));
// If I don't currently have a ball in my hand, then try to catch closest one
if (!ballAlreadyInHand && grabButtonPressed) {
@ -107,8 +104,11 @@ function checkControllerSide(whichSide) {
var ballPosition = getBallHoldPosition(whichSide);
var properties = { position: { x: ballPosition.x,
y: ballPosition.y,
z: ballPosition.z },
velocity : { x: 0, y: 0, z: 0}, inHand: true };
z: ballPosition.z },
color: HELD_COLOR,
velocity : { x: 0, y: 0, z: 0},
lifetime : 600,
inHand: true };
Particles.editParticle(closestParticle, properties);
var options = new AudioInjectionOptions();
@ -127,7 +127,7 @@ function checkControllerSide(whichSide) {
//}
// If '3' is pressed, and not holding a ball, make a new one
if (Controller.isButtonPressed(BUTTON_3) && !ballAlreadyInHand) {
if (grabButtonPressed && !ballAlreadyInHand) {
var ballPosition = getBallHoldPosition(whichSide);
var properties = { position: { x: ballPosition.x,
y: ballPosition.y,
@ -135,11 +135,11 @@ function checkControllerSide(whichSide) {
velocity: { x: 0, y: 0, z: 0},
gravity: { x: 0, y: 0, z: 0},
inHand: true,
radius: 0.05,
radius: BALL_RADIUS,
damping: 0.999,
color: { red: 255, green: 0, blue: 0 },
color: HELD_COLOR,
lifetime: 10 // 10 seconds - same as default, not needed but here as an example
lifetime: 600 // 10 seconds - same as default, not needed but here as an example
};
newParticle = Particles.addParticle(properties);
@ -155,7 +155,7 @@ function checkControllerSide(whichSide) {
var options = new AudioInjectionOptions();
options.position = ballPosition;
options.volume = 1.0;
Audio.playSound(catchSound, options);
Audio.playSound(newSound, options);
return; // exit early
}
@ -188,7 +188,9 @@ function checkControllerSide(whichSide) {
y: tipVelocity.y * THROWN_VELOCITY_SCALING,
z: tipVelocity.z * THROWN_VELOCITY_SCALING } ,
inHand: false,
gravity: { x: 0, y: -2, z: 0},
color: THROWN_COLOR,
lifetime: 10,
gravity: { x: 0, y: -GRAVITY_STRENGTH, z: 0},
};
Particles.editParticle(handParticle, properties);

View file

@ -12,7 +12,8 @@ QLabel#advancedTuningLabel {
QPushButton#buttonBrowseHead,
QPushButton#buttonBrowseBody,
QPushButton#buttonBrowseLocation {
QPushButton#buttonBrowseLocation,
QPushButton#buttonBrowseScriptsLocation {
background-image: url(styles/search.svg);
background-repeat: no-repeat;
background-position: center center;

View file

@ -134,7 +134,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_nodeThread(new QThread(this)),
_datagramProcessor(),
_frameCount(0),
_fps(120.0f),
_fps(60.0f),
_justStarted(true),
_voxelImporter(NULL),
_importSucceded(false),
@ -167,7 +167,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_nodeBoundsDisplay(this),
_previousScriptLocation(),
_applicationOverlay(),
_runningScriptsWidget(new RunningScriptsWidget(_window)),
_runningScriptsWidget(NULL),
_runningScriptsWidgetWasVisible(false),
_trayIcon(new QSystemTrayIcon(_window)),
_lastNackTime(usecTimestampNow())
@ -201,6 +201,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// call Menu getInstance static method to set up the menu
_window->setMenuBar(Menu::getInstance());
_runningScriptsWidget = new RunningScriptsWidget(_window);
unsigned int listenPort = 0; // bind to an ephemeral port by default
const char** constArgv = const_cast<const char**>(argv);
const char* portStr = getCmdOption(argc, constArgv, "--listenPort");
@ -551,7 +553,7 @@ void Application::initializeGL() {
}
// update before the first render
update(0.0f);
update(1.f / _fps);
InfoView::showFirstTime();
}
@ -3532,10 +3534,12 @@ void Application::saveScripts() {
_settings->endArray();
}
ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor) {
ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptName);
const QString& scriptURLString = scriptUrl.toString();
if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptURLString) && !_scriptEnginesHash[scriptURLString]->isFinished()){
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
@ -3548,10 +3552,11 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insert(scriptURLString, scriptEngine);
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
}
@ -3614,7 +3619,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
workerThread->start();
// restore the main window's active state
if (!loadScriptFromEditor) {
if (activateMainWindow && !loadScriptFromEditor) {
_window->activateWindow();
}
bumpSettings();
@ -3623,7 +3628,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
}
void Application::scriptFinished(const QString& scriptName) {
if (_scriptEnginesHash.remove(scriptName)) {
QHash<QString, ScriptEngine*>::iterator it = _scriptEnginesHash.find(scriptName);
if (it != _scriptEnginesHash.end()) {
_scriptEnginesHash.erase(it);
_runningScriptsWidget->scriptStopped(scriptName);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
bumpSettings();

View file

@ -324,7 +324,7 @@ public slots:
void loadScriptURLDialog();
void toggleLogDialog();
void initAvatarAndViewFrustum();
ScriptEngine* loadScript(const QString& fileNameString = QString(), bool loadScriptFromEditor = false);
ScriptEngine* loadScript(const QString& fileNameString = QString(), bool loadScriptFromEditor = false, bool activateMainWindow = false);
void scriptFinished(const QString& scriptName);
void stopAllScripts(bool restart = false);
void stopScript(const QString& scriptName);

View file

@ -317,8 +317,6 @@ void CameraScriptableObject::setMode(const QString& mode) {
}
if (currentMode != targetMode) {
_camera->setMode(targetMode);
const float DEFAULT_MODE_SHIFT_PERIOD = 0.5f; // half second
_camera->setModeShiftPeriod(DEFAULT_MODE_SHIFT_PERIOD);
}
}

View file

@ -42,9 +42,8 @@ public:
void setTargetPosition(const glm::vec3& t);
void setTightness(float t) { _tightness = t; }
void setTargetRotation(const glm::quat& rotation);
void setMode(CameraMode m);
void setModeShiftPeriod(float r);
void setMode(CameraMode m);
void setFieldOfView(float f);
void setAspectRatio(float a);
void setNearClip(float n);
@ -130,6 +129,7 @@ public:
public slots:
QString getMode() const;
void setMode(const QString& mode);
void setModeShiftPeriod(float r) {_camera->setModeShiftPeriod(r); }
void setPosition(const glm::vec3& value) { _camera->setTargetPosition(value);}
glm::vec3 getPosition() const { return _camera->getPosition(); }

View file

@ -108,6 +108,7 @@ Menu::Menu() :
_fastFPSAverage(ONE_SECOND_OF_FRAMES),
_loginAction(NULL),
_preferencesDialog(NULL),
_scriptsLocation(),
_loginDialog(NULL),
_snapshotsLocation()
{
@ -345,6 +346,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::StringHair, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
@ -607,6 +609,7 @@ void Menu::loadSettings(QSettings* settings) {
_boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0);
_snapshotsLocation = settings->value("snapshotsLocation",
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString();
setScriptsLocation(settings->value("scriptsLocation", QString()).toString());
settings->beginGroup("View Frustum Offset Camera");
// in case settings is corrupt or missing loadSetting() will check for NaN
@ -651,6 +654,7 @@ void Menu::saveSettings(QSettings* settings) {
settings->setValue("avatarLODDistanceMultiplier", _avatarLODDistanceMultiplier);
settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust);
settings->setValue("snapshotsLocation", _snapshotsLocation);
settings->setValue("scriptsLocation", _scriptsLocation);
settings->beginGroup("View Frustum Offset Camera");
settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw);
settings->setValue("viewFrustumOffsetPitch", _viewFrustumOffset.pitch);
@ -1786,3 +1790,8 @@ QString Menu::getSnapshotsLocation() const {
}
return _snapshotsLocation;
}
void Menu::setScriptsLocation(const QString& scriptsLocation) {
_scriptsLocation = scriptsLocation;
emit scriptLocationChanged(scriptsLocation);
}

View file

@ -102,6 +102,9 @@ public:
QString getSnapshotsLocation() const;
void setSnapshotsLocation(QString snapshotsLocation) { _snapshotsLocation = snapshotsLocation; }
const QString& getScriptsLocation() const { return _scriptsLocation; }
void setScriptsLocation(const QString& scriptsLocation);
BandwidthDialog* getBandwidthDialog() const { return _bandwidthDialog; }
FrustumDrawMode getFrustumDrawMode() const { return _frustumDrawMode; }
ViewFrustumOffset getViewFrustumOffset() const { return _viewFrustumOffset; }
@ -156,6 +159,9 @@ public:
void static goToDomain(const QString newDomain);
void static goTo(QString destination);
signals:
void scriptLocationChanged(const QString& newPath);
public slots:
void loginForCurrentDomain();
@ -283,6 +289,7 @@ private:
QPointer<LoginDialog> _loginDialog;
QAction* _chatAction;
QString _snapshotsLocation;
QString _scriptsLocation;
};
namespace MenuOption {
@ -319,6 +326,7 @@ namespace MenuOption {
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";
const QString BuckyBalls = "Bucky Balls";
const QString StringHair = "String Hair";
const QString CascadedShadows = "Cascaded";
const QString Chat = "Chat...";
const QString ChatCircling = "Chat Circling";

View file

@ -0,0 +1,209 @@
//
// ScriptsModel.cpp
// interface/src
//
// Created by Ryan Huffman on 05/12/14.
// Copyright 2014 High Fidelity, Inc.
//
// S3 request code written with ModelBrowser as a reference.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QNetworkAccessManager>
#include <QUrl>
#include <QUrlQuery>
#include <QXmlStreamReader>
#include "ScriptsModel.h"
#include "Menu.h"
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
static const QString PUBLIC_URL = "http://public.highfidelity.io";
static const QString MODELS_LOCATION = "scripts/";
static const QString PREFIX_PARAMETER_NAME = "prefix";
static const QString MARKER_PARAMETER_NAME = "marker";
static const QString IS_TRUNCATED_NAME = "IsTruncated";
static const QString CONTAINER_NAME = "Contents";
static const QString KEY_NAME = "Key";
static const int SCRIPT_PATH = Qt::UserRole;
ScriptItem::ScriptItem(const QString& filename, const QString& fullPath) :
_filename(filename),
_fullPath(fullPath) {
};
ScriptsModel::ScriptsModel(QObject* parent) :
QAbstractListModel(parent),
_loadingScripts(false),
_localDirectory(),
_fsWatcher(),
_localFiles(),
_remoteFiles() {
QString scriptPath = Menu::getInstance()->getScriptsLocation();
_localDirectory.setPath(scriptPath);
_localDirectory.setFilter(QDir::Files | QDir::Readable);
_localDirectory.setNameFilters(QStringList("*.js"));
_fsWatcher.addPath(_localDirectory.absolutePath());
connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles);
connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation);
reloadLocalFiles();
reloadRemoteFiles();
}
QVariant ScriptsModel::data(const QModelIndex& index, int role) const {
const QList<ScriptItem*>* files = NULL;
int row = 0;
bool isLocal = index.row() < _localFiles.size();
if (isLocal) {
files = &_localFiles;
row = index.row();
} else {
files = &_remoteFiles;
row = index.row() - _localFiles.size();
}
if (role == Qt::DisplayRole) {
return QVariant((*files)[row]->getFilename() + (isLocal ? " (local)" : ""));
} else if (role == ScriptPath) {
return QVariant((*files)[row]->getFullPath());
}
return QVariant();
}
int ScriptsModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
return _localFiles.length() + _remoteFiles.length();
}
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
_fsWatcher.removePath(_localDirectory.absolutePath());
_localDirectory.setPath(newPath);
_fsWatcher.addPath(_localDirectory.absolutePath());
reloadLocalFiles();
}
void ScriptsModel::reloadRemoteFiles() {
if (!_loadingScripts) {
_loadingScripts = true;
while (!_remoteFiles.isEmpty()) {
delete _remoteFiles.takeFirst();
}
requestRemoteFiles();
}
}
void ScriptsModel::requestRemoteFiles(QString marker) {
QUrl url(S3_URL);
QUrlQuery query;
query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION);
if (!marker.isEmpty()) {
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
}
url.setQuery(query);
QNetworkAccessManager* accessManager = new QNetworkAccessManager(this);
connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));
QNetworkRequest request(url);
accessManager->get(request);
}
void ScriptsModel::downloadFinished(QNetworkReply* reply) {
bool finished = true;
if (reply->error() == QNetworkReply::NoError) {
QByteArray data = reply->readAll();
if (!data.isEmpty()) {
finished = parseXML(data);
} else {
qDebug() << "Error: Received no data when loading remote scripts";
}
}
reply->deleteLater();
sender()->deleteLater();
if (finished) {
_loadingScripts = false;
}
}
bool ScriptsModel::parseXML(QByteArray xmlFile) {
beginResetModel();
QXmlStreamReader xml(xmlFile);
QRegExp jsRegex(".*\\.js");
bool truncated = false;
QString lastKey;
while (!xml.atEnd() && !xml.hasError()) {
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) {
while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) {
xml.readNext();
if (xml.text().toString() == "true") {
truncated = true;
}
}
}
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) {
while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) {
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) {
xml.readNext();
lastKey = xml.text().toString();
if (jsRegex.exactMatch(xml.text().toString())) {
_remoteFiles.append(new ScriptItem(lastKey.mid(MODELS_LOCATION.length()), S3_URL + "/" + lastKey));
}
}
xml.readNext();
}
}
xml.readNext();
}
endResetModel();
// Error handling
if (xml.hasError()) {
qDebug() << "Error loading remote scripts: " << xml.errorString();
return true;
}
if (truncated) {
requestRemoteFiles(lastKey);
}
// If this request was not truncated, we are done.
return !truncated;
}
void ScriptsModel::reloadLocalFiles() {
beginResetModel();
while (!_localFiles.isEmpty()) {
delete _localFiles.takeFirst();
}
_localDirectory.refresh();
const QFileInfoList localFiles = _localDirectory.entryInfoList();
for (int i = 0; i < localFiles.size(); i++) {
QFileInfo file = localFiles[i];
_localFiles.append(new ScriptItem(file.fileName(), file.absoluteFilePath()));
}
endResetModel();
}

View file

@ -0,0 +1,62 @@
//
// ScriptsModel.h
// interface/src
//
// Created by Ryan Huffman on 05/12/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_ScriptsModel_h
#define hifi_ScriptsModel_h
#include <QAbstractListModel>
#include <QDir>
#include <QNetworkReply>
#include <QFileSystemWatcher>
class ScriptItem {
public:
ScriptItem(const QString& filename, const QString& fullPath);
const QString& getFilename() { return _filename; };
const QString& getFullPath() { return _fullPath; };
private:
QString _filename;
QString _fullPath;
};
class ScriptsModel : public QAbstractListModel {
Q_OBJECT
public:
ScriptsModel(QObject* parent = NULL);
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
enum Role {
ScriptPath = Qt::UserRole,
};
protected slots:
void updateScriptsLocation(const QString& newPath);
void downloadFinished(QNetworkReply* reply);
void reloadLocalFiles();
void reloadRemoteFiles();
protected:
void requestRemoteFiles(QString marker = QString());
bool parseXML(QByteArray xmlFile);
private:
bool _loadingScripts;
QDir _localDirectory;
QFileSystemWatcher _fsWatcher;
QList<ScriptItem*> _localFiles;
QList<ScriptItem*> _remoteFiles;
};
#endif // hifi_ScriptsModel_h

View file

@ -68,6 +68,7 @@ void printVector(glm::vec3 vec) {
qDebug("%4.2f, %4.2f, %4.2f", vec.x, vec.y, vec.z);
}
// Return the azimuth angle (in radians) between two points.
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos) {
return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z);

View file

@ -48,6 +48,10 @@ Avatar::Avatar() :
_skeletonModel(this),
_bodyYawDelta(0.0f),
_velocity(0.0f, 0.0f, 0.0f),
_lastVelocity(0.0f, 0.0f, 0.0f),
_acceleration(0.0f, 0.0f, 0.0f),
_angularVelocity(0.0f, 0.0f, 0.0f),
_lastOrientation(),
_leanScale(0.5f),
_scale(1.0f),
_worldUpDirection(DEFAULT_UP_DIRECTION),
@ -76,6 +80,7 @@ void Avatar::init() {
_skeletonModel.init();
_initialized = true;
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
initializeHair();
}
glm::vec3 Avatar::getChestPosition() const {
@ -134,10 +139,15 @@ void Avatar::simulate(float deltaTime) {
head->setPosition(headPosition);
head->setScale(_scale);
head->simulate(deltaTime, false, _shouldRenderBillboard);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
simulateHair(deltaTime);
}
}
// update position by velocity, and subtract the change added earlier for gravity
_position += _velocity * deltaTime;
updateAcceleration(deltaTime);
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
@ -157,6 +167,17 @@ void Avatar::simulate(float deltaTime) {
}
}
void Avatar::updateAcceleration(float deltaTime) {
// Linear Component of Acceleration
_acceleration = (_velocity - _lastVelocity) * (1.f / deltaTime);
_lastVelocity = _velocity;
// Angular Component of Acceleration
glm::quat orientation = getOrientation();
glm::quat delta = glm::inverse(_lastOrientation) * orientation;
_angularVelocity = safeEulerAngles(delta) * (1.f / deltaTime);
_lastOrientation = getOrientation();
}
void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) {
_mouseRayOrigin = origin;
_mouseRayDirection = direction;
@ -357,6 +378,232 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
getHand()->render(false, modelRenderMode);
}
getHead()->render(1.0f, modelRenderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
renderHair();
}
}
//
// Constants for the Hair Simulation
//
const float HAIR_LENGTH = 0.2f;
const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS;
const float HAIR_DAMPING = 0.99f;
const float HEAD_RADIUS = 0.21f;
const float CONSTRAINT_RELAXATION = 10.0f;
const glm::vec3 HAIR_GRAVITY(0.0f, -0.007f, 0.0f);
const float HAIR_ACCELERATION_COUPLING = 0.025f;
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f;
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
const float HAIR_THICKNESS = 0.015f;
const float HAIR_STIFFNESS = 0.0000f;
const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f);
const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f);
const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.0f);
const float MAX_WIND_STRENGTH = 0.02f;
const float FINGER_LENGTH = 0.25f;
const float FINGER_RADIUS = 0.10f;
void Avatar::renderHair() {
//
// Render the avatar's moveable hair
//
glm::vec3 headPosition = getHead()->getPosition();
glPushMatrix();
glTranslatef(headPosition.x, headPosition.y, headPosition.z);
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glBegin(GL_QUADS);
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
for (int link = 0; link < HAIR_LINKS - 1; link++) {
int vertexIndex = strand * HAIR_LINKS + link;
glColor3fv(&_hairColors[vertexIndex].x);
glNormal3fv(&_hairNormals[vertexIndex].x);
glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
_hairPosition[vertexIndex].z - _hairQuadDelta[vertexIndex].z);
glVertex3f(_hairPosition[vertexIndex].x + _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex].y + _hairQuadDelta[vertexIndex].y,
_hairPosition[vertexIndex].z + _hairQuadDelta[vertexIndex].z);
glVertex3f(_hairPosition[vertexIndex + 1].x + _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex + 1].y + _hairQuadDelta[vertexIndex].y,
_hairPosition[vertexIndex + 1].z + _hairQuadDelta[vertexIndex].z);
glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
_hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
}
}
glEnd();
glPopMatrix();
}
void Avatar::simulateHair(float deltaTime) {
deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f);
glm::vec3 acceleration = getAcceleration();
if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) {
acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION;
}
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
acceleration = acceleration * rotation;
glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity();
// Get hand positions to allow touching hair
glm::vec3 leftHandPosition, rightHandPosition;
getSkeletonModel().getLeftHandPosition(leftHandPosition);
getSkeletonModel().getRightHandPosition(rightHandPosition);
leftHandPosition -= getHead()->getPosition();
rightHandPosition -= getHead()->getPosition();
glm::quat leftRotation, rightRotation;
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation);
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(leftRotation);
rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(rightRotation);
leftHandPosition = leftHandPosition * rotation;
rightHandPosition = rightHandPosition * rotation;
float windIntensity = randFloat() * MAX_WIND_STRENGTH;
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
for (int link = 0; link < HAIR_LINKS; link++) {
int vertexIndex = strand * HAIR_LINKS + link;
if (vertexIndex % HAIR_LINKS == 0) {
// Base Joint - no integration
} else {
//
// Vertlet Integration
//
// Add velocity from last position, with damping
glm::vec3 thisPosition = _hairPosition[vertexIndex];
glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex];
_hairPosition[vertexIndex] += diff * HAIR_DAMPING;
// Resolve collision with head sphere
if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) {
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
(HEAD_RADIUS - glm::length(_hairPosition[vertexIndex]));
}
// Resolve collision with hands
if (glm::length(_hairPosition[vertexIndex] - leftHandPosition) < FINGER_RADIUS) {
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - leftHandPosition) *
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - leftHandPosition));
}
if (glm::length(_hairPosition[vertexIndex] - rightHandPosition) < FINGER_RADIUS) {
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - rightHandPosition) *
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - rightHandPosition));
}
// Add a little gravity
_hairPosition[vertexIndex] += HAIR_GRAVITY * rotation * deltaTime;
// Add linear acceleration of the avatar body
_hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime;
// Add stiffness (like hair care products do)
_hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex])
* powf(1.f - link / HAIR_LINKS, 2.f) * HAIR_STIFFNESS;
// Add some wind
glm::vec3 wind = WIND_DIRECTION * windIntensity;
_hairPosition[vertexIndex] += wind * deltaTime;
const float ANGULAR_VELOCITY_MIN = 0.001f;
// Add angular acceleration of the avatar body
if (glm::length(angularVelocity) > ANGULAR_VELOCITY_MIN) {
glm::vec3 yawVector = _hairPosition[vertexIndex];
yawVector.y = 0.f;
if (glm::length(yawVector) > EPSILON) {
float radius = glm::length(yawVector);
yawVector = glm::normalize(yawVector);
float angle = atan2f(yawVector.x, -yawVector.z) + PI;
glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0));
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
}
glm::vec3 pitchVector = _hairPosition[vertexIndex];
pitchVector.x = 0.f;
if (glm::length(pitchVector) > EPSILON) {
float radius = glm::length(pitchVector);
pitchVector = glm::normalize(pitchVector);
float angle = atan2f(pitchVector.y, -pitchVector.z) + PI;
glm::vec3 delta = glm::vec3(0.0f, 1.0f, 0.f) * glm::angleAxis(angle, glm::vec3(1, 0, 0));
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.x * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
}
glm::vec3 rollVector = _hairPosition[vertexIndex];
rollVector.z = 0.f;
if (glm::length(rollVector) > EPSILON) {
float radius = glm::length(rollVector);
pitchVector = glm::normalize(rollVector);
float angle = atan2f(rollVector.x, rollVector.y) + PI;
glm::vec3 delta = glm::vec3(-1.0f, 0.0f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 0, 1));
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.z * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
}
}
// Iterate length constraints to other links
for (int link = 0; link < HAIR_MAX_CONSTRAINTS; link++) {
if (_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] > -1) {
// If there is a constraint, try to enforce it
glm::vec3 vectorBetween = _hairPosition[_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link]] - _hairPosition[vertexIndex];
_hairPosition[vertexIndex] += glm::normalize(vectorBetween) * (glm::length(vectorBetween) - HAIR_LINK_LENGTH) * CONSTRAINT_RELAXATION * deltaTime;
}
}
// Store start position for next vertlet pass
_hairLastPosition[vertexIndex] = thisPosition;
}
}
}
}
void Avatar::initializeHair() {
const float FACE_WIDTH = PI / 4.0f;
glm::vec3 thisVertex;
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
float strandAngle = randFloat() * PI;
float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH));
float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI);
glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
thisStrand *= HEAD_RADIUS;
for (int link = 0; link < HAIR_LINKS; link++) {
int vertexIndex = strand * HAIR_LINKS + link;
// Clear constraints
for (int link = 0; link < HAIR_MAX_CONSTRAINTS; link++) {
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] = -1;
}
if (vertexIndex % HAIR_LINKS == 0) {
// start of strand
thisVertex = thisStrand;
} else {
thisVertex+= glm::normalize(thisStrand) * HAIR_LINK_LENGTH;
// Set constraints to vertex before and maybe vertex after in strand
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS] = vertexIndex - 1;
if (link < (HAIR_LINKS - 1)) {
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + 1] = vertexIndex + 1;
}
}
_hairPosition[vertexIndex] = thisVertex;
_hairLastPosition[vertexIndex] = _hairPosition[vertexIndex];
_hairOriginalPosition[vertexIndex] = _hairPosition[vertexIndex];
_hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * HAIR_THICKNESS, 0.f, sin(strandAngle) * HAIR_THICKNESS);
_hairQuadDelta[vertexIndex] *= 1.f - (link / HAIR_LINKS);
_hairNormals[vertexIndex] = glm::normalize(randVector());
if (randFloat() < elevation / PI_OVER_TWO) {
_hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)HAIR_LINKS);
} else {
_hairColors[vertexIndex] = HAIR_COLOR2 * ((float)(link + 1) / (float)HAIR_LINKS);
}
}
}
qDebug() << "Initialize Hair";
}
bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const {

View file

@ -32,6 +32,10 @@ static const float RESCALING_TOLERANCE = .02f;
extern const float CHAT_MESSAGE_SCALE;
extern const float CHAT_MESSAGE_HEIGHT;
const int HAIR_STRANDS = 150; // Number of strands of hair
const int HAIR_LINKS = 10; // Number of links in a hair strand
const int HAIR_MAX_CONSTRAINTS = 2; // Hair verlet is connected to at most how many others
enum DriveKeys {
FWD = 0,
BACK,
@ -145,6 +149,9 @@ public:
Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const;
Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const;
glm::vec3 getAcceleration() const { return _acceleration; }
glm::vec3 getAngularVelocity() const { return _angularVelocity; }
public slots:
void updateCollisionGroups();
@ -156,6 +163,10 @@ protected:
QVector<Model*> _attachmentModels;
float _bodyYawDelta;
glm::vec3 _velocity;
glm::vec3 _lastVelocity;
glm::vec3 _acceleration;
glm::vec3 _angularVelocity;
glm::quat _lastOrientation;
float _leanScale;
float _scale;
glm::vec3 _worldUpDirection;
@ -172,6 +183,7 @@ protected:
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setScale(float scale);
void updateAcceleration(float deltaTime);
float getSkeletonHeight() const;
float getHeadHeight() const;
@ -187,6 +199,18 @@ protected:
virtual void renderAttachments(RenderMode renderMode);
virtual void updateJointMappings();
glm::vec3 _hairPosition[HAIR_STRANDS * HAIR_LINKS];
glm::vec3 _hairOriginalPosition[HAIR_STRANDS * HAIR_LINKS];
glm::vec3 _hairLastPosition[HAIR_STRANDS * HAIR_LINKS];
glm::vec3 _hairQuadDelta[HAIR_STRANDS * HAIR_LINKS];
glm::vec3 _hairNormals[HAIR_STRANDS * HAIR_LINKS];
glm::vec3 _hairColors[HAIR_STRANDS * HAIR_LINKS];
int _hairIsMoveable[HAIR_STRANDS * HAIR_LINKS];
int _hairConstraints[HAIR_STRANDS * HAIR_LINKS * 2]; // Hair can link to two others
void renderHair();
void simulateHair(float deltaTime);
void initializeHair();
private:
@ -198,6 +222,7 @@ private:
void renderBillboard();
float getBillboardSize() const;
};
#endif // hifi_Avatar_h

View file

@ -194,6 +194,13 @@ void MyAvatar::simulate(float deltaTime) {
head->setScale(_scale);
head->simulate(deltaTime, true);
}
{
PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate");
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
simulateHair(deltaTime);
}
}
{
PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll");
@ -368,6 +375,7 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
if (!_shouldRender) {
return; // exit early
}
Avatar::render(cameraPosition, renderMode);
// don't display IK constraints in shadow mode
@ -420,6 +428,25 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const {
}
}
const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
glm::vec3 MyAvatar::getLeftPalmPosition() {
glm::vec3 leftHandPosition;
getSkeletonModel().getLeftHandPosition(leftHandPosition);
glm::quat leftRotation;
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation);
leftHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftRotation);
return leftHandPosition;
}
glm::vec3 MyAvatar::getRightPalmPosition() {
glm::vec3 rightHandPosition;
getSkeletonModel().getRightHandPosition(rightHandPosition);
glm::quat rightRotation;
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
rightHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(rightRotation);
return rightHandPosition;
}
void MyAvatar::setLocalGravity(glm::vec3 gravity) {
_motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
// Environmental and Local gravities are incompatible. Since Local is being set here
@ -831,6 +858,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
// Render head so long as the camera isn't inside it
if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) {
getHead()->render(1.0f, modelRenderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
renderHair();
}
}
getHand()->render(true, modelRenderMode);
}
@ -881,11 +911,19 @@ void MyAvatar::updateOrientation(float deltaTime) {
float yaw, pitch, roll;
OculusManager::getEulerAngles(yaw, pitch, roll);
// ... so they need to be converted to degrees before we do math...
yaw *= DEGREES_PER_RADIAN;
pitch *= DEGREES_PER_RADIAN;
roll *= DEGREES_PER_RADIAN;
// Record the angular velocity
Head* head = getHead();
head->setBaseYaw(yaw * DEGREES_PER_RADIAN);
head->setBasePitch(pitch * DEGREES_PER_RADIAN);
head->setBaseRoll(roll * DEGREES_PER_RADIAN);
glm::vec3 angularVelocity(yaw - head->getBaseYaw(), pitch - head->getBasePitch(), roll - head->getBaseRoll());
head->setAngularVelocity(angularVelocity);
head->setBaseYaw(yaw);
head->setBasePitch(pitch);
head->setBaseRoll(roll);
}
// update the euler angles
@ -974,6 +1012,7 @@ void MyAvatar::updatePosition(float deltaTime) {
} else {
_position += _velocity * deltaTime;
}
updateAcceleration(deltaTime);
}
// update moving flag based on speed

View file

@ -139,7 +139,10 @@ public slots:
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void updateMotionBehaviorsFromMenu();
glm::vec3 getLeftPalmPosition();
glm::vec3 getRightPalmPosition();
signals:
void transformChanged();

View file

@ -637,12 +637,15 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
// compute the default transforms and slam the ragdoll positions accordingly
// (which puts the shapes where we want them)
transforms[0] = _jointStates[0].getTransform();
_ragdollPoints[0]._position = extractTranslation(transforms[0]);
_ragdollPoints[0]._lastPosition = _ragdollPoints[0]._position;
for (int i = 1; i < numJoints; i++) {
for (int i = 0; i < numJoints; i++) {
const FBXJoint& joint = geometry.joints.at(i);
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
transforms[i] = _jointStates[i].getTransform();
_ragdollPoints[i]._position = extractTranslation(transforms[i]);
_ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position;
continue;
}
assert(parentIndex != -1);
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;

View file

@ -39,9 +39,10 @@ inline float min(float a, float b) {
ApplicationOverlay::ApplicationOverlay() :
_framebufferObject(NULL),
_textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
_crosshairTexture(0),
_alpha(1.0f),
_active(true) {
_active(true),
_crosshairTexture(0)
{
memset(_reticleActive, 0, sizeof(_reticleActive));
memset(_magActive, 0, sizeof(_reticleActive));

View file

@ -771,7 +771,7 @@ int VoxelizationVisitor::visit(MetavoxelInfo& info) {
}
return DEFAULT_ORDER;
}
QRgb closestColor;
QRgb closestColor = QRgb();
float closestDistance = FLT_MAX;
for (unsigned int i = 0; i < sizeof(DIRECTION_ROTATIONS) / sizeof(DIRECTION_ROTATIONS[0]); i++) {
glm::vec3 rotated = DIRECTION_ROTATIONS[i] * center;

View file

@ -29,6 +29,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : F
connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser);
connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser);
connect(ui.buttonBrowseLocation, &QPushButton::clicked, this, &PreferencesDialog::openSnapshotLocationBrowser);
connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser);
connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked,
Application::getInstance(), &Application::loadDefaultScripts);
}
@ -72,13 +73,32 @@ void PreferencesDialog::openBodyModelBrowser() {
void PreferencesDialog::openSnapshotLocationBrowser() {
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
show();
QString dir = QFileDialog::getExistingDirectory(this, tr("Snapshots Location"),
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!dir.isNull() && !dir.isEmpty()) {
ui.snapshotLocationEdit->setText(dir);
}
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
show();
}
void PreferencesDialog::openScriptsLocationBrowser() {
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
show();
QString dir = QFileDialog::getExistingDirectory(this, tr("Scripts Location"),
ui.scriptsLocationEdit->text(),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!dir.isNull() && !dir.isEmpty()) {
ui.scriptsLocationEdit->setText(dir);
}
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
show();
}
void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) {
@ -118,6 +138,8 @@ void PreferencesDialog::loadPreferences() {
ui.snapshotLocationEdit->setText(menuInstance->getSnapshotsLocation());
ui.scriptsLocationEdit->setText(menuInstance->getScriptsLocation());
ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() *
ui.pupilDilationSlider->maximum());
@ -180,6 +202,10 @@ void PreferencesDialog::savePreferences() {
Menu::getInstance()->setSnapshotsLocation(ui.snapshotLocationEdit->text());
}
if (!ui.scriptsLocationEdit->text().isEmpty() && QDir(ui.scriptsLocationEdit->text()).exists()) {
Menu::getInstance()->setScriptsLocation(ui.scriptsLocationEdit->text());
}
myAvatar->getHead()->setPupilDilation(ui.pupilDilationSlider->value() / (float)ui.pupilDilationSlider->maximum());
myAvatar->setLeanScale(ui.leanScaleSpin->value());
myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value());

View file

@ -42,6 +42,7 @@ private slots:
void setHeadUrl(QString modelUrl);
void setSkeletonUrl(QString modelUrl);
void openSnapshotLocationBrowser();
void openScriptsLocationBrowser();
};

View file

@ -3,6 +3,7 @@
// interface/src/ui
//
// Created by Mohammed Nafees on 03/28/2014.
// Updated by Ryan Huffman on 05/13/2014.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@ -12,18 +13,27 @@
#include "ui_runningScriptsWidget.h"
#include "RunningScriptsWidget.h"
#include <QAbstractProxyModel>
#include <QFileInfo>
#include <QKeyEvent>
#include <QPainter>
#include <QTableWidgetItem>
#include "Application.h"
#include "Menu.h"
#include "ScriptsModel.h"
RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
FramelessDialog(parent, 0, POSITION_LEFT),
ui(new Ui::RunningScriptsWidget) {
ui(new Ui::RunningScriptsWidget),
_signalMapper(this),
_scriptsModel(this),
_proxyModel(this) {
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, false);
setAllowResize(false);
ui->hideWidgetButton->setIcon(QIcon(Application::resourcesPath() + "images/close.svg"));
@ -31,17 +41,24 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
ui->stopAllButton->setIcon(QIcon(Application::resourcesPath() + "images/stop.svg"));
ui->loadScriptButton->setIcon(QIcon(Application::resourcesPath() + "images/plus-white.svg"));
_runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget);
_runningScriptsTable->setColumnCount(2);
_runningScriptsTable->setColumnWidth(0, 245);
_runningScriptsTable->setColumnWidth(1, 22);
connect(_runningScriptsTable, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript);
ui->recentlyLoadedScriptsArea->hide();
ui->filterLineEdit->installEventFilter(this);
connect(&_proxyModel, &QSortFilterProxyModel::modelReset,
this, &RunningScriptsWidget::selectFirstInList);
_proxyModel.setSourceModel(&_scriptsModel);
_proxyModel.sort(0, Qt::AscendingOrder);
_proxyModel.setDynamicSortFilter(true);
ui->scriptListView->setModel(&_proxyModel);
connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter);
connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList);
_recentlyLoadedScriptsTable = new ScriptsTableWidget(ui->recentlyLoadedScriptsTableWidget);
_recentlyLoadedScriptsTable->setColumnCount(1);
_recentlyLoadedScriptsTable->setColumnWidth(0, 265);
connect(_recentlyLoadedScriptsTable, &QTableWidget::cellClicked,
this, &RunningScriptsWidget::loadScript);
connect(ui->hideWidgetButton, &QPushButton::clicked,
Application::getInstance(), &Application::toggleRunningScriptsWidget);
@ -51,59 +68,121 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
this, &RunningScriptsWidget::allScriptsStopped);
connect(ui->loadScriptButton, &QPushButton::clicked,
Application::getInstance(), &Application::loadDialog);
connect(&_signalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(stopScript(const QString&)));
}
RunningScriptsWidget::~RunningScriptsWidget() {
delete ui;
}
void RunningScriptsWidget::updateFileFilter(const QString& filter) {
QRegExp regex("^.*" + QRegExp::escape(filter) + ".*$", Qt::CaseInsensitive);
_proxyModel.setFilterRegExp(regex);
selectFirstInList();
}
void RunningScriptsWidget::loadScriptFromList(const QModelIndex& index) {
QVariant scriptFile = _proxyModel.data(index, ScriptsModel::ScriptPath);
Application::getInstance()->loadScript(scriptFile.toString(), false, false);
}
void RunningScriptsWidget::loadSelectedScript() {
QModelIndex selectedIndex = ui->scriptListView->currentIndex();
if (selectedIndex.isValid()) {
loadScriptFromList(selectedIndex);
}
}
void RunningScriptsWidget::setBoundary(const QRect& rect) {
_boundary = rect;
}
void RunningScriptsWidget::setRunningScripts(const QStringList& list) {
_runningScriptsTable->setRowCount(list.size());
setUpdatesEnabled(false);
QLayoutItem* widget;
while ((widget = ui->scrollAreaWidgetContents->layout()->takeAt(0)) != NULL) {
delete widget->widget();
delete widget;
}
const int CLOSE_ICON_HEIGHT = 12;
for (int i = 0; i < list.size(); i++) {
QWidget* row = new QWidget(ui->scrollAreaWidgetContents);
row->setLayout(new QHBoxLayout(row));
QUrl url = QUrl(list.at(i));
QLabel* name = new QLabel(url.fileName(), row);
QPushButton* closeButton = new QPushButton(row);
closeButton->setFlat(true);
closeButton->setIcon(
QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(CLOSE_ICON_HEIGHT)));
closeButton->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred));
closeButton->setStyleSheet("border: 0;");
closeButton->setCursor(Qt::PointingHandCursor);
connect(closeButton, SIGNAL(clicked()), &_signalMapper, SLOT(map()));
_signalMapper.setMapping(closeButton, url.toString());
row->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
row->layout()->setContentsMargins(4, 4, 4, 4);
row->layout()->setSpacing(0);
row->layout()->addWidget(name);
row->layout()->addWidget(closeButton);
row->setToolTip(url.toString());
QFrame* line = new QFrame(row);
line->setFrameShape(QFrame::HLine);
line->setStyleSheet("color: #E1E1E1; margin-left: 6px; margin-right: 6px;");
ui->scrollAreaWidgetContents->layout()->addWidget(row);
ui->scrollAreaWidgetContents->layout()->addWidget(line);
}
ui->noRunningScriptsLabel->setVisible(list.isEmpty());
ui->currentlyRunningLabel->setVisible(!list.isEmpty());
ui->runningScriptsTableWidget->setVisible(!list.isEmpty());
ui->reloadAllButton->setVisible(!list.isEmpty());
ui->stopAllButton->setVisible(!list.isEmpty());
const int CLOSE_ICON_HEIGHT = 12;
ui->scrollAreaWidgetContents->updateGeometry();
setUpdatesEnabled(true);
Application::processEvents();
repaint();
}
for (int i = 0; i < list.size(); ++i) {
QTableWidgetItem *scriptName = new QTableWidgetItem;
scriptName->setText(QFileInfo(list.at(i)).fileName());
scriptName->setToolTip(list.at(i));
scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
QTableWidgetItem *closeIcon = new QTableWidgetItem;
closeIcon->setIcon(QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(CLOSE_ICON_HEIGHT)));
_runningScriptsTable->setItem(i, 0, scriptName);
_runningScriptsTable->setItem(i, 1, closeIcon);
void RunningScriptsWidget::showEvent(QShowEvent* event) {
if (!event->spontaneous()) {
ui->filterLineEdit->setFocus();
}
const int RUNNING_SCRIPTS_TABLE_LEFT_MARGIN = 12;
const int RECENTLY_LOADED_TOP_MARGIN = 61;
const int RECENTLY_LOADED_LABEL_TOP_MARGIN = 19;
FramelessDialog::showEvent(event);
}
int y = ui->runningScriptsTableWidget->y() + RUNNING_SCRIPTS_TABLE_LEFT_MARGIN;
for (int i = 0; i < _runningScriptsTable->rowCount(); ++i) {
y += _runningScriptsTable->rowHeight(i);
void RunningScriptsWidget::selectFirstInList() {
if (_proxyModel.rowCount() > 0) {
ui->scriptListView->setCurrentIndex(_proxyModel.index(0, 0));
}
}
bool RunningScriptsWidget::eventFilter(QObject* sender, QEvent* event) {
if (sender == ui->filterLineEdit) {
if (event->type() != QEvent::KeyPress) {
return false;
}
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
QModelIndex selectedIndex = ui->scriptListView->currentIndex();
if (selectedIndex.isValid()) {
loadScriptFromList(selectedIndex);
}
event->accept();
return true;
}
return false;
}
ui->runningScriptsTableWidget->resize(ui->runningScriptsTableWidget->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN);
_runningScriptsTable->resize(_runningScriptsTable->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN);
ui->reloadAllButton->move(ui->reloadAllButton->x(), y);
ui->stopAllButton->move(ui->stopAllButton->x(), y);
ui->recentlyLoadedLabel->move(ui->recentlyLoadedLabel->x(),
ui->stopAllButton->y() + ui->stopAllButton->height() + RECENTLY_LOADED_TOP_MARGIN);
ui->recentlyLoadedScriptsTableWidget->move(ui->recentlyLoadedScriptsTableWidget->x(),
ui->recentlyLoadedLabel->y() + RECENTLY_LOADED_LABEL_TOP_MARGIN);
createRecentlyLoadedScriptsTable();
return FramelessDialog::eventFilter(sender, event);
}
void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) {
@ -114,79 +193,10 @@ void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) {
}
}
void RunningScriptsWidget::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.setPen(QColor::fromRgb(225, 225, 225)); // #e1e1e1
if (ui->currentlyRunningLabel->isVisible()) {
// line below the 'Currently Running' label
painter.drawLine(36, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height(),
300, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height());
}
if (ui->recentlyLoadedLabel->isVisible()) {
// line below the 'Recently loaded' label
painter.drawLine(36, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height(),
300, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height());
}
painter.end();
}
void RunningScriptsWidget::scriptStopped(const QString& scriptName) {
_recentlyLoadedScripts.prepend(scriptName);
}
void RunningScriptsWidget::stopScript(int row, int column) {
if (column == 1) { // make sure the user has clicked on the close icon
_lastStoppedScript = _runningScriptsTable->item(row, 0)->toolTip();
emit stopScriptName(_runningScriptsTable->item(row, 0)->toolTip());
}
}
void RunningScriptsWidget::loadScript(int row, int column) {
Application::getInstance()->loadScript(_recentlyLoadedScriptsTable->item(row, column)->toolTip());
// _recentlyLoadedScripts.prepend(scriptName);
}
void RunningScriptsWidget::allScriptsStopped() {
Application::getInstance()->stopAllScripts();
}
void RunningScriptsWidget::createRecentlyLoadedScriptsTable() {
if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) {
_recentlyLoadedScripts.prepend(_lastStoppedScript);
_lastStoppedScript = "";
}
for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) {
if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) {
_recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i));
}
}
ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty());
ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty());
ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty());
int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size();
_recentlyLoadedScriptsTable->setRowCount(limit);
for (int i = 0; i < limit; i++) {
QTableWidgetItem *scriptName = new QTableWidgetItem;
scriptName->setText(QString::number(i + 1) + ". " + QFileInfo(_recentlyLoadedScripts.at(i)).fileName());
scriptName->setToolTip(_recentlyLoadedScripts.at(i));
scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
_recentlyLoadedScriptsTable->setItem(i, 0, scriptName);
}
int y = ui->recentlyLoadedScriptsTableWidget->y() + 15;
for (int i = 0; i < _recentlyLoadedScriptsTable->rowCount(); ++i) {
y += _recentlyLoadedScriptsTable->rowHeight(i);
}
ui->recentlyLoadedInstruction->setGeometry(36, y,
ui->recentlyLoadedInstruction->width(),
ui->recentlyLoadedInstruction->height());
repaint();
}

View file

@ -3,6 +3,7 @@
// interface/src/ui
//
// Created by Mohammed Nafees on 03/28/2014.
// Updated by Ryan Huffman on 05/13/2014.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@ -12,6 +13,11 @@
#ifndef hifi_RunningScriptsWidget_h
#define hifi_RunningScriptsWidget_h
#include <QFileSystemModel>
#include <QSignalMapper>
#include <QSortFilterProxyModel>
#include "ScriptsModel.h"
#include "FramelessDialog.h"
#include "ScriptsTableWidget.h"
@ -32,27 +38,31 @@ signals:
void stopScriptName(const QString& name);
protected:
virtual bool eventFilter(QObject* sender, QEvent* event);
virtual void keyPressEvent(QKeyEvent* event);
virtual void paintEvent(QPaintEvent* event);
virtual void showEvent(QShowEvent* event);
public slots:
void scriptStopped(const QString& scriptName);
void setBoundary(const QRect& rect);
private slots:
void stopScript(int row, int column);
void loadScript(int row, int column);
void allScriptsStopped();
void updateFileFilter(const QString& filter);
void loadScriptFromList(const QModelIndex& index);
void loadSelectedScript();
void selectFirstInList();
private:
Ui::RunningScriptsWidget* ui;
ScriptsTableWidget* _runningScriptsTable;
QSignalMapper _signalMapper;
QSortFilterProxyModel _proxyModel;
ScriptsModel _scriptsModel;
ScriptsTableWidget* _recentlyLoadedScriptsTable;
QStringList _recentlyLoadedScripts;
QString _lastStoppedScript;
QRect _boundary;
void createRecentlyLoadedScriptsTable();
};
#endif // hifi_RunningScriptsWidget_h

View file

@ -154,9 +154,9 @@ color: #0e7077</string>
<property name="geometry">
<rect>
<x>0</x>
<y>-1002</y>
<width>477</width>
<height>1386</height>
<y>0</y>
<width>600</width>
<height>1091</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -645,6 +645,112 @@ color: #0e7077</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="headLabel_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077</string>
</property>
<property name="text">
<string>Load scripts from this directory:</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="buddy">
<cstring>snapshotLocationEdit</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QLineEdit" name="scriptsLocationEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_11">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonBrowseScriptsLocation">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_12">

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>323</width>
<height>894</height>
<width>324</width>
<height>971</height>
</rect>
</property>
<property name="windowTitle">
@ -21,240 +21,623 @@ QWidget {
background: #f7f7f7;
}</string>
</property>
<widget class="QLabel" name="widgetTitle">
<property name="geometry">
<rect>
<x>37</x>
<y>20</y>
<width>251</width>
<height>31</height>
</rect>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>20</number>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
<property name="topMargin">
<number>20</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>20</number>
</property>
<item>
<widget class="QWidget" name="header" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="widgetTitle">
<property name="styleSheet">
<string notr="true">color: #0e7077;
font-size: 20px;
background: transparent;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18px;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="indent">
<number>-1</number>
</property>
</widget>
<widget class="QLabel" name="currentlyRunningLabel">
<property name="geometry">
<rect>
<x>36</x>
<y>110</y>
<width>270</width>
<height>20</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 14px;
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18px;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="indent">
<number>-1</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="hideWidgetButton">
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">border: 0</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="runningScriptsArea" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>141</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="currentlyRunningLabel">
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>16</pointsize>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 16px;
background: transparent;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QPushButton" name="reloadAllButton">
<property name="geometry">
<rect>
<x>36</x>
<y>270</y>
<width>111</width>
<height>35</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QWidget" name="reloadStopButtonArea" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>24</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="reloadAllButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>111</width>
<height>35</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 4px;
font: bold 14px;
padding-top: 3px;</string>
</property>
<property name="text">
<string>Reload all</string>
</property>
</widget>
<widget class="QPushButton" name="stopAllButton">
<property name="geometry">
<rect>
<x>160</x>
<y>270</y>
<width>93</width>
<height>35</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
</property>
<property name="text">
<string>Reload all</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopAllButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>111</width>
<height>35</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 4px;
font: bold 14px;
padding-top: 3px;</string>
</property>
<property name="text">
<string>Stop all</string>
</property>
</widget>
<widget class="QLabel" name="recentlyLoadedLabel">
<property name="geometry">
<rect>
<x>36</x>
<y>320</y>
<width>265</width>
<height>20</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 14px;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QLabel" name="recentlyLoadedInstruction">
<property name="geometry">
<rect>
<x>36</x>
<y>630</y>
<width>211</width>
<height>41</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #95a5a6;
</property>
<property name="text">
<string>Stop all</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="noRunningScriptsLabel">
<property name="styleSheet">
<string notr="true">font: 14px;</string>
</property>
<property name="text">
<string>There are no scripts currently running.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QScrollArea" name="runningScriptsList">
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>14</pointsize>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">margin: 0;</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>269</width>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font-size: 14px;</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="recentlyLoadedScriptsArea" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="recentlyLoadedLabel">
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 16px;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="noRecentlyLoadedLabel">
<property name="styleSheet">
<string notr="true">font: 14px;</string>
</property>
<property name="text">
<string>There are no recently loaded scripts.</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="recentlyLoadedScriptsTableWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>284</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">background: transparent;
font-size: 14px;</string>
</property>
<property name="text">
<string>(click a script to load and run it)</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="hideWidgetButton">
<property name="geometry">
<rect>
<x>285</x>
<y>29</y>
<width>16</width>
<height>16</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="noRunningScriptsLabel">
<property name="geometry">
<rect>
<x>36</x>
<y>110</y>
<width>271</width>
<height>51</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">font: 14px;</string>
</property>
<property name="text">
<string>There are no scripts currently running.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QWidget" name="recentlyLoadedScriptsTableWidget" native="true">
<property name="geometry">
<rect>
<x>30</x>
<y>340</y>
<width>272</width>
<height>280</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">background: transparent;
font-size: 14px;</string>
</property>
</widget>
<widget class="QWidget" name="runningScriptsTableWidget" native="true">
<property name="geometry">
<rect>
<x>30</x>
<y>128</y>
<width>272</width>
<height>161</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">background: transparent;
font-size: 14px;</string>
</property>
</widget>
<widget class="QPushButton" name="loadScriptButton">
<property name="geometry">
<rect>
<x>36</x>
<y>70</y>
<width>111</width>
<height>35</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
</property>
<zorder>runningScriptsList</zorder>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="quickLoadArea" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 16px;</string>
</property>
<property name="text">
<string>Scripts</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="loadScriptButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>111</width>
<height>35</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 4px;
font: bold 14px;
padding-top: 3px;</string>
</property>
<property name="text">
<string>Load script</string>
</property>
</widget>
<zorder>widgetTitle</zorder>
<zorder>currentlyRunningLabel</zorder>
<zorder>recentlyLoadedLabel</zorder>
<zorder>recentlyLoadedInstruction</zorder>
<zorder>hideWidgetButton</zorder>
<zorder>recentlyLoadedScriptsTableWidget</zorder>
<zorder>runningScriptsTableWidget</zorder>
<zorder>noRunningScriptsLabel</zorder>
<zorder>reloadAllButton</zorder>
<zorder>stopAllButton</zorder>
<zorder>loadScriptButton</zorder>
</property>
<property name="text">
<string>Load script</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filterLineEdit">
<property name="styleSheet">
<string notr="true">border: 1px solid rgb(128, 128, 128);
border-radius: 2px;
padding: 4px;
background-color: white;</string>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>filter</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QListView" name="scriptListView">
<property name="styleSheet">
<string notr="true">QListView {
border: 1px solid rgb(128, 128, 128);
border-radius: 2px;
}
QListView::item {
padding-top: 2px;
padding-bottom: 2px;
}</string>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>

View file

@ -23,6 +23,9 @@ const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE;
const int DEFAULT_MAX_PACKET_SIZE = 3000;
// the default slow-start threshold, which will be lowered quickly when we first encounter packet loss
const float DEFAULT_SLOW_START_THRESHOLD = 1000.0f;
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* parent) :
QObject(parent),
_outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
@ -37,7 +40,12 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject*
_incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly),
_inputStream(_incomingPacketStream),
_receivedHighPriorityMessages(0),
_maxPacketSize(DEFAULT_MAX_PACKET_SIZE) {
_maxPacketSize(DEFAULT_MAX_PACKET_SIZE),
_packetsPerGroup(1.0f),
_packetsToWrite(0.0f),
_slowStartThreshold(DEFAULT_SLOW_START_THRESHOLD),
_packetRateIncreasePacketNumber(0),
_packetRateDecreasePacketNumber(0) {
_outgoingPacketStream.setByteOrder(QDataStream::LittleEndian);
_incomingDatagramStream.setByteOrder(QDataStream::LittleEndian);
@ -71,6 +79,33 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) {
return channel;
}
int DatagramSequencer::startPacketGroup(int desiredPackets) {
// figure out how much data we have enqueued and increase the number of packets desired
int totalAvailable = 0;
foreach (ReliableChannel* channel, _reliableOutputChannels) {
totalAvailable += channel->getBytesAvailable();
}
desiredPackets += (totalAvailable / _maxPacketSize);
// increment our packet counter and subtract/return the integer portion
_packetsToWrite += _packetsPerGroup;
int wholePackets = (int)_packetsToWrite;
_packetsToWrite -= wholePackets;
wholePackets = qMin(wholePackets, desiredPackets);
// if we don't want to send any more, push out the rate increase number past the group
if (desiredPackets <= _packetsPerGroup) {
_packetRateIncreasePacketNumber = _outgoingPacketNumber + wholePackets + 1;
}
// likewise, if we're only sending one packet, don't let its loss cause rate decrease
if (wholePackets == 1) {
_packetRateDecreasePacketNumber = _outgoingPacketNumber + 2;
}
return wholePackets;
}
Bitstream& DatagramSequencer::startPacket() {
// start with the list of acknowledgements
_outgoingPacketStream << (quint32)_receiveRecords.size();
@ -172,7 +207,10 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
if (index < 0 || index >= _sendRecords.size()) {
continue;
}
QList<SendRecord>::iterator it = _sendRecords.begin() + index;
QList<SendRecord>::iterator it = _sendRecords.begin();
for (int i = 0; i < index; i++) {
sendRecordLost(*it++);
}
sendRecordAcknowledged(*it);
emit sendAcknowledged(index);
_sendRecords.erase(_sendRecords.begin(), it + 1);
@ -253,6 +291,28 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
foreach (const ChannelSpan& span, record.spans) {
getReliableOutputChannel(span.channel)->spanAcknowledged(span);
}
// increase the packet rate with every ack until we pass the slow start threshold; then, every round trip
if (record.packetNumber >= _packetRateIncreasePacketNumber) {
if (_packetsPerGroup >= _slowStartThreshold) {
_packetRateIncreasePacketNumber = _outgoingPacketNumber + 1;
}
_packetsPerGroup += 1.0f;
}
}
void DatagramSequencer::sendRecordLost(const SendRecord& record) {
// notify the channels of their lost spans
foreach (const ChannelSpan& span, record.spans) {
getReliableOutputChannel(span.channel)->spanLost(record.packetNumber, _outgoingPacketNumber + 1);
}
// halve the rate and remember as threshold
if (record.packetNumber >= _packetRateDecreasePacketNumber) {
_packetsPerGroup = qMax(_packetsPerGroup * 0.5f, 1.0f);
_slowStartThreshold = _packetsPerGroup;
_packetRateDecreasePacketNumber = _outgoingPacketNumber + 1;
}
}
void DatagramSequencer::appendReliableData(int bytes, QVector<ChannelSpan>& spans) {
@ -520,7 +580,9 @@ int SpanList::set(int offset, int length) {
// look for an intersection within the list
int position = 0;
for (QList<Span>::iterator it = _spans.begin(); it != _spans.end(); it++) {
for (int i = 0; i < _spans.size(); i++) {
QList<Span>::iterator it = _spans.begin() + i;
// if we intersect the unset portion, contract it
position += it->unset;
if (offset <= position) {
@ -530,16 +592,20 @@ int SpanList::set(int offset, int length) {
// if we continue into the set portion, expand it and consume following spans
int extra = offset + length - position;
if (extra >= 0) {
int amount = setSpans(it + 1, extra);
it->set += amount;
_totalSet += amount;
extra -= it->set;
it->set += remove;
_totalSet += remove;
if (extra > 0) {
int amount = setSpans(it + 1, extra);
_spans[i].set += amount;
_totalSet += amount;
}
// otherwise, insert a new span
} else {
Span span = { it->unset, length + extra };
_spans.insert(it, span);
Span span = { it->unset, length };
it->unset = -extra;
_totalSet += span.set;
_spans.insert(it, span);
_totalSet += length;
}
return 0;
}
@ -548,9 +614,11 @@ int SpanList::set(int offset, int length) {
position += it->set;
if (offset <= position) {
int extra = offset + length - position;
int amount = setSpans(it + 1, extra);
it->set += amount;
_totalSet += amount;
if (extra > 0) {
int amount = setSpans(it + 1, extra);
_spans[i].set += amount;
_totalSet += amount;
}
return 0;
}
}
@ -619,6 +687,7 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
_priority(1.0f),
_offset(0),
_writePosition(0),
_writePositionResetPacketNumber(0),
_messagesEnabled(true) {
_buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
@ -629,67 +698,76 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
}
void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
// find out how many spans we want to write
int spanCount = 0;
int remainingBytes = bytes;
bool first = true;
while (remainingBytes > 0) {
int position = 0;
foreach (const SpanList::Span& span, _acknowledged.getSpans()) {
if (remainingBytes <= 0) {
break;
}
spanCount++;
remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, span.unset));
position += (span.unset + span.set);
}
int leftover = _buffer.pos() - position;
if (remainingBytes > 0 && leftover > 0) {
spanCount++;
remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, leftover));
}
if (bytes == 0) {
out << (quint32)0;
return;
}
// write the count and the spans
out << (quint32)spanCount;
remainingBytes = bytes;
first = true;
while (remainingBytes > 0) {
_writePosition %= _buffer.pos();
while (bytes > 0) {
int position = 0;
foreach (const SpanList::Span& span, _acknowledged.getSpans()) {
if (remainingBytes <= 0) {
break;
for (int i = 0; i < _acknowledged.getSpans().size(); i++) {
const SpanList::Span& span = _acknowledged.getSpans().at(i);
position += span.unset;
if (_writePosition < position) {
int start = qMax(position - span.unset, _writePosition);
int length = qMin(bytes, position - start);
writeSpan(out, start, length, spans);
writeFullSpans(out, bytes - length, i + 1, position + span.set, spans);
out << (quint32)0;
return;
}
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, span.unset), spans);
position += (span.unset + span.set);
position += span.set;
}
int leftover = _buffer.pos() - position;
if (remainingBytes > 0 && leftover > 0) {
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, leftover), spans);
position = _buffer.pos();
if (_writePosition < position && leftover > 0) {
int start = qMax(position - leftover, _writePosition);
int length = qMin(bytes, position - start);
writeSpan(out, start, length, spans);
writeFullSpans(out, bytes - length, 0, 0, spans);
out << (quint32)0;
return;
}
_writePosition = 0;
}
}
void ReliableChannel::writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position,
QVector<DatagramSequencer::ChannelSpan>& spans) {
int expandedSize = _acknowledged.getSpans().size() + 1;
for (int i = 0; i < expandedSize; i++) {
if (bytes == 0) {
return;
}
int index = (startingIndex + i) % expandedSize;
if (index == _acknowledged.getSpans().size()) {
int leftover = _buffer.pos() - position;
if (leftover > 0) {
int length = qMin(leftover, bytes);
writeSpan(out, position, length, spans);
bytes -= length;
}
position = 0;
} else {
const SpanList::Span& span = _acknowledged.getSpans().at(index);
int length = qMin(span.unset, bytes);
writeSpan(out, position, length, spans);
bytes -= length;
position += (span.unset + span.set);
}
}
}
int ReliableChannel::getBytesToWrite(bool& first, int length) const {
if (first) {
first = false;
return length - (_writePosition % length);
}
return length;
}
int ReliableChannel::writeSpan(QDataStream& out, bool& first, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans) {
if (first) {
first = false;
position = _writePosition % length;
length -= position;
_writePosition += length;
}
int ReliableChannel::writeSpan(QDataStream& out, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans) {
DatagramSequencer::ChannelSpan span = { _index, _offset + position, length };
spans.append(span);
out << (quint32)span.offset;
out << (quint32)length;
out << (quint32)span.offset;
_buffer.writeToStream(position, length, out);
_writePosition = position + length;
return length;
}
@ -700,17 +778,28 @@ void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& spa
_buffer.seek(_buffer.size());
_offset += advancement;
_writePosition = qMax(_writePosition - advancement, 0);
}
_writePosition = qMax(_writePosition - advancement, 0);
}
}
void ReliableChannel::spanLost(int packetNumber, int nextOutgoingPacketNumber) {
// reset the write position up to once each round trip time
if (packetNumber >= _writePositionResetPacketNumber) {
_writePosition = 0;
_writePositionResetPacketNumber = nextOutgoingPacketNumber;
}
}
void ReliableChannel::readData(QDataStream& in) {
quint32 segments;
in >> segments;
bool readSome = false;
for (quint32 i = 0; i < segments; i++) {
quint32 offset, size;
in >> offset >> size;
forever {
quint32 size;
in >> size;
if (size == 0) {
break;
}
quint32 offset;
in >> offset;
int position = offset - _offset;
int end = position + size;

View file

@ -99,6 +99,11 @@ public:
/// Returns the intput channel at the specified index, creating it if necessary.
ReliableChannel* getReliableInputChannel(int index = 0);
/// Starts a packet group.
/// \param desiredPackets the number of packets we'd like to write in the group
/// \return the number of packets to write in the group
int startPacketGroup(int desiredPackets = 1);
/// Starts a new packet for transmission.
/// \return a reference to the Bitstream to use for writing to the packet
Bitstream& startPacket();
@ -165,6 +170,9 @@ private:
/// Notes that the described send was acknowledged by the other party.
void sendRecordAcknowledged(const SendRecord& record);
/// Notes that the described send was lost in transit.
void sendRecordLost(const SendRecord& record);
/// Appends some reliable data to the outgoing packet.
void appendReliableData(int bytes, QVector<ChannelSpan>& spans);
@ -200,6 +208,12 @@ private:
int _maxPacketSize;
float _packetsPerGroup;
float _packetsToWrite;
float _slowStartThreshold;
int _packetRateIncreasePacketNumber;
int _packetRateDecreasePacketNumber;
QHash<int, ReliableChannel*> _reliableOutputChannels;
QHash<int, ReliableChannel*> _reliableInputChannels;
};
@ -343,10 +357,12 @@ private:
ReliableChannel(DatagramSequencer* sequencer, int index, bool output);
void writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans);
int getBytesToWrite(bool& first, int length) const;
int writeSpan(QDataStream& out, bool& first, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans);
void writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position,
QVector<DatagramSequencer::ChannelSpan>& spans);
int writeSpan(QDataStream& out, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans);
void spanAcknowledged(const DatagramSequencer::ChannelSpan& span);
void spanLost(int packetNumber, int nextOutgoingPacketNumber);
void readData(QDataStream& in);
@ -359,6 +375,7 @@ private:
int _offset;
int _writePosition;
int _writePositionResetPacketNumber;
SpanList _acknowledged;
bool _messagesEnabled;
};

View file

@ -1094,14 +1094,14 @@ const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1;
int MetavoxelVisitor::encodeRandomOrder() {
// see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_.22inside-out.22_algorithm
int order;
int order = 0;
int randomValues = rand();
for (int i = 0, iShift = 0; i < MetavoxelNode::CHILD_COUNT; i++, iShift += ORDER_ELEMENT_BITS) {
int j = (randomValues >> iShift) % (i + 1);
int jShift = j * ORDER_ELEMENT_BITS;
if (j != i) {
int jValue = (order >> jShift) & ORDER_ELEMENT_MASK;
order = (order & ~(ORDER_ELEMENT_MASK << iShift)) | (jValue << iShift);
order |= (jValue << iShift);
}
order = (order & ~(ORDER_ELEMENT_MASK << jShift)) | (i << jShift);
}

View file

@ -314,8 +314,9 @@ void ScriptEngine::evaluate() {
if (_engine.hasUncaughtException()) {
int line = _engine.uncaughtExceptionLineNumber();
qDebug() << "Uncaught exception at line" << line << ":" << result.toString();
emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString());
qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
_engine.clearExceptions();
}
}
@ -324,7 +325,7 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN
bool hasUncaughtException = _engine.hasUncaughtException();
if (hasUncaughtException) {
int line = _engine.uncaughtExceptionLineNumber();
qDebug() << "Uncaught exception at line" << line << ": " << result.toString();
qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ": " << result.toString();
}
emit evaluationFinished(result, hasUncaughtException);
_engine.clearExceptions();
@ -353,9 +354,9 @@ void ScriptEngine::run() {
QScriptValue result = _engine.evaluate(_scriptContents);
if (_engine.hasUncaughtException()) {
int line = _engine.uncaughtExceptionLineNumber();
qDebug() << "Uncaught exception at line" << line << ":" << result.toString();
emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString());
qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
_engine.clearExceptions();
}
QElapsedTimer startTime;
@ -495,8 +496,9 @@ void ScriptEngine::run() {
if (_engine.hasUncaughtException()) {
int line = _engine.uncaughtExceptionLineNumber();
qDebug() << "Uncaught exception at line" << line << ":" << _engine.uncaughtException().toString();
emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + _engine.uncaughtException().toString());
qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << _engine.uncaughtException().toString();
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + _engine.uncaughtException().toString());
_engine.clearExceptions();
}
}
emit scriptEnding();
@ -656,5 +658,6 @@ void ScriptEngine::include(const QString& includeFile) {
int line = _engine.uncaughtExceptionLineNumber();
qDebug() << "Uncaught exception at (" << includeFile << ") line" << line << ":" << result.toString();
emit errorMessage("Uncaught exception at (" + includeFile + ") line" + QString::number(line) + ":" + result.toString());
_engine.clearExceptions();
}
}

View file

@ -28,12 +28,127 @@ MetavoxelTests::MetavoxelTests(int& argc, char** argv) :
QCoreApplication(argc, argv) {
}
static bool testSpanList() {
SpanList list;
if (list.getTotalSet() != 0 || !list.getSpans().isEmpty()) {
qDebug() << "Failed empty state test.";
return true;
}
if (list.set(-5, 15) != 10 || list.getTotalSet() != 0 || !list.getSpans().isEmpty()) {
qDebug() << "Failed initial front set.";
return true;
}
if (list.set(5, 15) != 0 || list.getTotalSet() != 15 || list.getSpans().size() != 1 ||
list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 15) {
qDebug() << "Failed initial middle set.";
return true;
}
if (list.set(25, 5) != 0 || list.getTotalSet() != 20 || list.getSpans().size() != 2 ||
list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 15 ||
list.getSpans().at(1).unset != 5 || list.getSpans().at(1).set != 5) {
qDebug() << "Failed initial end set.";
return true;
}
if (list.set(1, 3) != 0 || list.getTotalSet() != 23 || list.getSpans().size() != 3 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 15 ||
list.getSpans().at(2).unset != 5 || list.getSpans().at(2).set != 5) {
qDebug() << "Failed second front set.";
return true;
}
SpanList threeSet = list;
if (list.set(20, 5) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
qDebug() << "Failed minimal join last two.";
return true;
}
list = threeSet;
if (list.set(5, 25) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
qDebug() << "Failed maximal join last two.";
return true;
}
list = threeSet;
if (list.set(10, 18) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
qDebug() << "Failed middle join last two.";
return true;
}
list = threeSet;
if (list.set(10, 18) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
qDebug() << "Failed middle join last two.";
return true;
}
list = threeSet;
if (list.set(2, 26) != 0 || list.getTotalSet() != 29 || list.getSpans().size() != 1 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 29) {
qDebug() << "Failed middle join three.";
return true;
}
list = threeSet;
if (list.set(0, 2) != 4 || list.getTotalSet() != 20 || list.getSpans().size() != 2 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 15 ||
list.getSpans().at(1).unset != 5 || list.getSpans().at(1).set != 5) {
qDebug() << "Failed front advance.";
return true;
}
list = threeSet;
if (list.set(-10, 15) != 20 || list.getTotalSet() != 5 || list.getSpans().size() != 1 ||
list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 5) {
qDebug() << "Failed middle advance.";
return true;
}
list = threeSet;
if (list.set(-10, 38) != 30 || list.getTotalSet() != 0 || list.getSpans().size() != 0) {
qDebug() << "Failed end advance.";
return true;
}
list = threeSet;
if (list.set(-10, 100) != 90 || list.getTotalSet() != 0 || list.getSpans().size() != 0) {
qDebug() << "Failed clobber advance.";
return true;
}
list = threeSet;
if (list.set(21, 3) != 0 || list.getTotalSet() != 26 || list.getSpans().size() != 4 ||
list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 15 ||
list.getSpans().at(2).unset != 1 || list.getSpans().at(2).set != 3 ||
list.getSpans().at(3).unset != 1 || list.getSpans().at(3).set != 5) {
qDebug() << "Failed adding fourth.";
return true;
}
return false;
}
static int datagramsSent = 0;
static int datagramsReceived = 0;
static int bytesSent = 0;
static int bytesReceived = 0;
static int maxDatagramsPerPacket = 0;
static int maxBytesPerPacket = 0;
static int groupsSent = 0;
static int maxPacketsPerGroup = 0;
static int highPriorityMessagesSent = 0;
static int highPriorityMessagesReceived = 0;
static int unreliableMessagesSent = 0;
@ -332,10 +447,19 @@ bool MetavoxelTests::run() {
QStringList arguments = this->arguments();
int test = (arguments.size() > 1) ? arguments.at(1).toInt() : 0;
if (test == 0 || test == 1) {
qDebug() << "Running SpanList test...";
qDebug();
if (testSpanList()) {
return true;
}
}
QByteArray datagramHeader("testheader");
const int SIMULATION_ITERATIONS = 10000;
if (test == 0 || test == 1) {
qDebug() << "Running transmission tests...";
if (test == 0 || test == 2) {
qDebug() << "Running transmission test...";
qDebug();
// create two endpoints with the same header
@ -364,8 +488,40 @@ bool MetavoxelTests::run() {
qDebug();
}
if (test == 0 || test == 2) {
qDebug() << "Running serialization tests...";
if (test == 0 || test == 3) {
qDebug() << "Running congestion control test...";
qDebug();
// clear the stats
streamedBytesSent = streamedBytesReceived = datagramsSent = bytesSent = 0;
datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0;
// create two endpoints with the same header
Endpoint alice(datagramHeader, Endpoint::CONGESTION_MODE), bob(datagramHeader, Endpoint::CONGESTION_MODE);
alice.setOther(&bob);
bob.setOther(&alice);
// perform a large number of simulation iterations
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
if (alice.simulate(i) || bob.simulate(i)) {
return true;
}
}
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
qDebug() << "Sent" << datagramsSent << "datagrams in" << groupsSent << "groups with" << bytesSent <<
"bytes, received" << datagramsReceived << "with" << bytesReceived << "bytes";
qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
qDebug() << "Max" << maxPacketsPerGroup << "packets per group";
qDebug() << "Average" << (bytesReceived / datagramsReceived) << "bytes per datagram," <<
(datagramsSent / groupsSent) << "datagrams per group";
qDebug() << "Speed:" << (bytesReceived / SIMULATION_ITERATIONS) << "bytes per iteration";
qDebug() << "Efficiency:" << ((float)streamedBytesReceived / bytesReceived);
}
if (test == 0 || test == 4) {
qDebug() << "Running serialization test...";
qDebug();
if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
@ -373,8 +529,8 @@ bool MetavoxelTests::run() {
}
}
if (test == 0 || test == 3) {
qDebug() << "Running metavoxel data tests...";
if (test == 0 || test == 5) {
qDebug() << "Running metavoxel data test...";
qDebug();
// clear the stats
@ -498,9 +654,22 @@ Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) :
ReliableChannel* output = _sequencer->getReliableOutputChannel(1);
output->setPriority(0.25f);
output->setMessagesEnabled(false);
const int MIN_STREAM_BYTES = 100000;
const int MAX_STREAM_BYTES = 200000;
QByteArray bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES);
QByteArray bytes;
if (mode == CONGESTION_MODE) {
const int HUGE_STREAM_BYTES = 60 * 1024 * 1024;
bytes = createRandomBytes(HUGE_STREAM_BYTES, HUGE_STREAM_BYTES);
// initialize the pipeline
for (int i = 0; i < 10; i++) {
_pipeline.append(ByteArrayVector());
}
_remainingPipelineCapacity = 100 * 1024;
} else {
const int MIN_STREAM_BYTES = 100000;
const int MAX_STREAM_BYTES = 200000;
bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES);
}
_dataStreamed.append(bytes);
output->getBuffer().write(bytes);
streamedBytesSent += bytes.size();
@ -633,10 +802,9 @@ int MutateVisitor::visit(MetavoxelInfo& info) {
bool Endpoint::simulate(int iterationNumber) {
// update/send our delayed datagrams
for (QList<QPair<QByteArray, int> >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
for (QList<ByteArrayIntPair>::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
if (it->second-- == 1) {
_other->_sequencer->receivedDatagram(it->first);
datagramsReceived++;
_other->receiveDatagram(it->first);
it = _delayedDatagrams.erase(it);
} else {
@ -646,7 +814,37 @@ bool Endpoint::simulate(int iterationNumber) {
int oldDatagramsSent = datagramsSent;
int oldBytesSent = bytesSent;
if (_mode == METAVOXEL_CLIENT_MODE) {
if (_mode == CONGESTION_MODE) {
// cycle our pipeline
ByteArrayVector datagrams = _pipeline.takeLast();
_pipeline.prepend(ByteArrayVector());
foreach (const QByteArray& datagram, datagrams) {
_sequencer->receivedDatagram(datagram);
datagramsReceived++;
bytesReceived += datagram.size();
_remainingPipelineCapacity += datagram.size();
}
int packetCount = _sequencer->startPacketGroup();
groupsSent++;
maxPacketsPerGroup = qMax(maxPacketsPerGroup, packetCount);
for (int i = 0; i < packetCount; i++) {
oldDatagramsSent = datagramsSent;
oldBytesSent = bytesSent;
Bitstream& out = _sequencer->startPacket();
out << QVariant();
_sequencer->endPacket();
maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber() };
_sendRecords.append(record);
}
return false;
} else if (_mode == METAVOXEL_CLIENT_MODE) {
Bitstream& out = _sequencer->startPacket();
ClientStateMessage state = { _lod };
@ -748,29 +946,28 @@ void Endpoint::sendDatagram(const QByteArray& datagram) {
// some datagrams are dropped
const float DROP_PROBABILITY = 0.1f;
if (randFloat() < DROP_PROBABILITY) {
float probabilityMultiplier = (_mode == CONGESTION_MODE) ? 0.01f : 1.0f;
if (randFloat() < DROP_PROBABILITY * probabilityMultiplier) {
return;
}
// some are received out of order
const float REORDER_PROBABILITY = 0.1f;
if (randFloat() < REORDER_PROBABILITY) {
if (randFloat() < REORDER_PROBABILITY * probabilityMultiplier) {
const int MIN_DELAY = 1;
const int MAX_DELAY = 5;
// have to copy the datagram; the one we're passed is a reference to a shared buffer
_delayedDatagrams.append(QPair<QByteArray, int>(QByteArray(datagram.constData(), datagram.size()),
_delayedDatagrams.append(ByteArrayIntPair(QByteArray(datagram.constData(), datagram.size()),
randIntInRange(MIN_DELAY, MAX_DELAY)));
// and some are duplicated
const float DUPLICATE_PROBABILITY = 0.01f;
if (randFloat() > DUPLICATE_PROBABILITY) {
if (randFloat() > DUPLICATE_PROBABILITY * probabilityMultiplier) {
return;
}
}
_other->_sequencer->receivedDatagram(datagram);
datagramsReceived++;
bytesReceived += datagram.size();
_other->receiveDatagram(datagram);
}
void Endpoint::handleHighPriorityMessage(const QVariant& message) {
@ -788,6 +985,15 @@ void Endpoint::handleHighPriorityMessage(const QVariant& message) {
}
void Endpoint::readMessage(Bitstream& in) {
if (_mode == CONGESTION_MODE) {
QVariant message;
in >> message;
// record the receipt
ReceiveRecord record = { _sequencer->getIncomingPacketNumber() };
_receiveRecords.append(record);
return;
}
if (_mode == METAVOXEL_CLIENT_MODE) {
QVariant message;
in >> message;
@ -887,6 +1093,20 @@ void Endpoint::clearReceiveRecordsBefore(int index) {
_receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
}
void Endpoint::receiveDatagram(const QByteArray& datagram) {
if (_mode == CONGESTION_MODE) {
if (datagram.size() <= _remainingPipelineCapacity) {
// have to copy the datagram; the one we're passed is a reference to a shared buffer
_pipeline[0].append(QByteArray(datagram.constData(), datagram.size()));
_remainingPipelineCapacity -= datagram.size();
}
} else {
_sequencer->receivedDatagram(datagram);
datagramsReceived++;
bytesReceived += datagram.size();
}
}
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
int userType = message.userType();
if (userType == ClientStateMessage::Type) {

View file

@ -40,7 +40,7 @@ class Endpoint : public QObject {
public:
enum Mode { BASIC_PEER_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE };
enum Mode { BASIC_PEER_MODE, CONGESTION_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE };
Endpoint(const QByteArray& datagramHeader, Mode mode = BASIC_PEER_MODE);
@ -63,6 +63,8 @@ private slots:
private:
void receiveDatagram(const QByteArray& datagram);
void handleMessage(const QVariant& message, Bitstream& in);
class SendRecord {
@ -96,7 +98,14 @@ private:
SharedObjectPointer _sphere;
Endpoint* _other;
QList<QPair<QByteArray, int> > _delayedDatagrams;
typedef QPair<QByteArray, int> ByteArrayIntPair;
QList<ByteArrayIntPair> _delayedDatagrams;
typedef QVector<QByteArray> ByteArrayVector;
QList<ByteArrayVector> _pipeline;
int _remainingPipelineCapacity;
float _highPriorityMessagesToSend;
QVariantList _highPriorityMessagesSent;
QList<SequencedTestMessage> _unreliableMessagesSent;