mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into sit_on_a_model
This commit is contained in:
commit
0b0cbdf340
36 changed files with 2093 additions and 503 deletions
|
@ -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
67
examples/concertCamera.js
Normal 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);
|
38
examples/inWorldTestTone.js
Normal file
38
examples/inWorldTestTone.js
Normal 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);
|
||||
|
|
@ -195,6 +195,8 @@ function keyReleaseEvent(event) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function mousePressEvent(event) {
|
||||
if (alt && !isActive) {
|
||||
mouseLastX = event.x;
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
209
interface/src/ScriptsModel.cpp
Normal file
209
interface/src/ScriptsModel.cpp
Normal 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();
|
||||
}
|
62
interface/src/ScriptsModel.h
Normal file
62
interface/src/ScriptsModel.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -139,7 +139,10 @@ public slots:
|
|||
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
|
||||
|
||||
void updateMotionBehaviorsFromMenu();
|
||||
|
||||
|
||||
glm::vec3 getLeftPalmPosition();
|
||||
glm::vec3 getRightPalmPosition();
|
||||
|
||||
signals:
|
||||
void transformChanged();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -42,6 +42,7 @@ private slots:
|
|||
void setHeadUrl(QString modelUrl);
|
||||
void setSkeletonUrl(QString modelUrl);
|
||||
void openSnapshotLocationBrowser();
|
||||
void openScriptsLocationBrowser();
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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><html><head/><body><p><span style=" font-size:18px;">Running Scripts</span></p></body></html></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><html><head/><body><p><span style=" font-size:18px;">Running Scripts</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html></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/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue