From fe74ec3f89f3a8be875de527311b5c91efaeed4a Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Fri, 31 Jul 2015 14:42:45 -0700 Subject: [PATCH 001/170] copied starting point from render-utils-test --- tests/gpu-test/CMakeLists.txt | 13 ++ tests/gpu-test/src/main.cpp | 219 ++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 tests/gpu-test/CMakeLists.txt create mode 100644 tests/gpu-test/src/main.cpp diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt new file mode 100644 index 0000000000..9f423e3645 --- /dev/null +++ b/tests/gpu-test/CMakeLists.txt @@ -0,0 +1,13 @@ + +set(TARGET_NAME gpu-test) + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui OpenGL) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +#include_oglplus() + +# link in the shared libraries +link_hifi_libraries(render-utils gpu shared) + +copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp new file mode 100644 index 0000000000..1724e368d8 --- /dev/null +++ b/tests/gpu-test/src/main.cpp @@ -0,0 +1,219 @@ +// +// main.cpp +// tests/gpu-test/src +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#include + +class RateCounter { + std::vector times; + QElapsedTimer timer; +public: + RateCounter() { + timer.start(); + } + + void reset() { + times.clear(); + } + + unsigned int count() const { + return times.size() - 1; + } + + float elapsed() const { + if (times.size() < 1) { + return 0.0f; + } + float elapsed = *times.rbegin() - *times.begin(); + return elapsed; + } + + void increment() { + times.push_back(timer.elapsed() / 1000.0f); + } + + float rate() const { + if (elapsed() == 0.0f) { + return NAN; + } + return (float) count() / elapsed(); + } +}; + + +const QString& getQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/")) + "/"; + qDebug() << "Qml Path: " << dir; + } + return dir; +} + +// Create a simple OpenGL window that renders text in various ways +class QTestWindow : public QWindow { + Q_OBJECT + + QOpenGLContext* _context{ nullptr }; + QSize _size; + //TextRenderer* _textRenderer[4]; + RateCounter fps; + +protected: + void renderText(); + +private: + void resizeWindow(const QSize& size) { + _size = size; + } + +public: + QTestWindow() { + setSurfaceType(QSurface::OpenGLSurface); + + QSurfaceFormat format; + // Qt Quick may need a depth and stencil buffer. Always make sure these are available. + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + format.setVersion(4, 5); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + format.setOption(QSurfaceFormat::DebugContext); + + setFormat(format); + + _context = new QOpenGLContext; + _context->setFormat(format); + _context->create(); + + show(); + makeCurrent(); + + gpu::Context::init(); + + + + { + QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); + logger->initialize(); // initializes in the current context, i.e. ctx + logger->enableMessages(); + connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; + }); + // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } + qDebug() << (const char*)glGetString(GL_VERSION); + + //_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); + //_textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, + // TextRenderer::SHADOW_EFFECT); + //_textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1, + // false, TextRenderer::OUTLINE_EFFECT); + //_textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0.2f, 0.2f, 0.2f, 1); + glDisable(GL_DEPTH_TEST); + + makeCurrent(); + + setFramePosition(QPoint(-1000, 0)); + resize(QSize(800, 600)); + } + + virtual ~QTestWindow() { + } + + void draw(); + void makeCurrent() { + _context->makeCurrent(this); + } + +protected: + + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } +}; + +#ifndef SERIF_FONT_FAMILY +#define SERIF_FONT_FAMILY "Times New Roman" +#endif + +//static const wchar_t* EXAMPLE_TEXT = L"Hello"; +//static const wchar_t* EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; +static const glm::uvec2 QUAD_OFFSET(10, 10); + +static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, { + 1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } }; + + +void QTestWindow::draw() { + if (!isVisible()) { + return; + } + + makeCurrent(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); + + _context->swapBuffers(this); + glFinish(); + + fps.increment(); + if (fps.elapsed() >= 2.0f) { + qDebug() << "FPS: " << fps.rate(); + fps.reset(); + } +} + +int main(int argc, char** argv) { + QGuiApplication app(argc, argv); + QTestWindow window; + QTimer timer; + timer.setInterval(1); + app.connect(&timer, &QTimer::timeout, &app, [&] { + window.draw(); + }); + timer.start(); + app.exec(); + return 0; +} + +#include "main.moc" From 883aa7af8dac1f83734803259325b2d9451dfdf5 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Mon, 3 Aug 2015 11:58:38 -0700 Subject: [PATCH 002/170] changed gl calls to gpu:: --- tests/gpu-test/src/main.cpp | 163 ++++++++++++++++++++++++++++-------- 1 file changed, 128 insertions(+), 35 deletions(-) diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 1724e368d8..a4a68c1d84 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -21,20 +21,27 @@ #include #include +#include +#include +#include +//#include + #include -#include +//#include #include -#include -#include -#include -#include +//#include +//#include +//#include +//#include #include #include #include #include +#include + #include @@ -74,6 +81,15 @@ public: } }; +const char * basicVS = +" varying vec3 pos; " +" void main(void) { " +" gl_Position.xyz = pos; " +" } "; +const char * basicFS = +" void main(void) { " +" gl_FragColor.xyz = vec3(0.7, 0.2, 0.5); " +" } "; const QString& getQmlDir() { static QString dir; @@ -90,8 +106,14 @@ const QString& getQmlDir() { class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext* _context{ nullptr }; + QOpenGLContext* _qGlContext{ nullptr }; QSize _size; + + gpu::ContextPointer _context; + gpu::PipelinePointer _pipeline; + gpu::BufferPointer _buffer; + gpu::Stream::FormatPointer _format; + //TextRenderer* _textRenderer[4]; RateCounter fps; @@ -117,27 +139,62 @@ public: setFormat(format); - _context = new QOpenGLContext; - _context->setFormat(format); - _context->create(); + _qGlContext = new QOpenGLContext; + _qGlContext->setFormat(format); + _qGlContext->create(); show(); makeCurrent(); gpu::Context::init(); - - - - { - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); - logger->initialize(); // initializes in the current context, i.e. ctx - logger->enableMessages(); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { - qDebug() << debugMessage; - }); - // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + _context = std::make_shared(); + + // Clear screen + gpu::Batch batch; + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 }); + _context->render(batch); + + // Create default shaders + + std::string vsSource (basicVS); + std::string fsSource (basicFS); + + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(vsSource)); + auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(fsSource)); + auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); + + gpu::Shader::BindingSet bindings; + if (!gpu::Shader::makeProgram(*shader, bindings)) { + printf("Could not compile shader"); + exit(-1); } - qDebug() << (const char*)glGetString(GL_VERSION); + +// auto shader = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + + auto state = std::make_shared(); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); + + float z = 10.0f; + const glm::vec3 vertices[] = { + { -1.0f, 1.0f, z }, + { 1.0f, -1.0f, z }, + { -1.0f, -1.0f, z } + }; + _buffer = std::make_shared(sizeof(vertices), (const gpu::Byte*)vertices); + _format = std::make_shared(); + _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + + +// { +// QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); +// logger->initialize(); // initializes in the current context, i.e. ctx +// logger->enableMessages(); +// connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { +// qDebug() << debugMessage; +// }); +// // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); +// } +// qDebug() << (const char*)glGetString(GL_VERSION); //_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); //_textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, @@ -146,12 +203,14 @@ public: // false, TextRenderer::OUTLINE_EFFECT); //_textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glClearColor(0.2f, 0.2f, 0.2f, 1); - glDisable(GL_DEPTH_TEST); +// glEnable(GL_BLEND); +// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +// glClearColor(0.2f, 0.2f, 0.2f, 1); +// glDisable(GL_DEPTH_TEST); makeCurrent(); + + _context->syncCache(); setFramePosition(QPoint(-1000, 0)); resize(QSize(800, 600)); @@ -162,7 +221,7 @@ public: void draw(); void makeCurrent() { - _context->makeCurrent(this); + _qGlContext->makeCurrent(this); } protected: @@ -180,8 +239,12 @@ protected: //static const wchar_t* EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; static const glm::uvec2 QUAD_OFFSET(10, 10); -static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, { - 1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } }; +static const glm::vec3 COLORS[4] = { + { 1.0, 1.0, 1.0 }, + { 0.5, 1.0, 0.5 }, + { 1.0, 0.5, 0.5 }, + { 0.5, 0.5, 1.0 } +}; void QTestWindow::draw() { @@ -190,15 +253,45 @@ void QTestWindow::draw() { } makeCurrent(); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); - - _context->swapBuffers(this); - glFinish(); + + gpu::Batch batch; + static int frameNum = 0; + frameNum++; + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, float(frameNum % 60)/60.0f, 0.5, 1.0 }); +// _context->render(batch); + + +//// batch.clear(); +// batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); +// + glm::quat cubeOrientation; +// + batch.setViewTransform(Transform()); + batch.setProjectionTransform(glm::mat4()); +// batch.setProjectionTransform(_viewFrustrum->getProjection()); + batch.setModelTransform(Transform().setRotation(cubeOrientation)); + batch.setPipeline(_pipeline); + batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); + batch.setInputFormat(_format); + batch.draw(gpu::TRIANGLES, 3); + _context->render(batch); +// +//// gpu::Stream::Format format; +//// format.setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::Vec3, gpu::FLOAT, gpu::XYZ)); +//// batch.setInputBuffer(gpu::Stream::POSITION, _trianglePosBuffer, ) +// +// +//// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +//// glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); +// +//// _context->swapBuffers(this); +//// glFinish(); +// + _qGlContext->swapBuffers(this); fps.increment(); if (fps.elapsed() >= 2.0f) { - qDebug() << "FPS: " << fps.rate(); + qDebug() << "FPS: " << fps.rate(); // This prints out the frames per 2 secs (ie. half of the actual fps) bug...? fps.reset(); } } @@ -207,7 +300,7 @@ int main(int argc, char** argv) { QGuiApplication app(argc, argv); QTestWindow window; QTimer timer; - timer.setInterval(1); + timer.setInterval(1000 / 120.0f); app.connect(&timer, &QTimer::timeout, &app, [&] { window.draw(); }); From 4e3cfd7c494c338b44f977946dea1fdba43a7235 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Mon, 3 Aug 2015 12:04:02 -0700 Subject: [PATCH 003/170] clear empty frame buffer fix thanks sam! --- libraries/gpu/src/gpu/GLBackendOutput.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp index d75d0cf521..94cfd0fe88 100755 --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -231,14 +231,21 @@ void GLBackend::do_clearFramebuffer(Batch& batch, uint32 paramOffset) { std::vector drawBuffers; if (masks & Framebuffer::BUFFER_COLORS) { - for (unsigned int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) { - if (masks & (1 << i)) { - drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i); + if (_output._framebuffer) { + for (unsigned int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) { + if (masks & (1 << i)) { + drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i); + } } - } - if (!drawBuffers.empty()) { - glDrawBuffers(drawBuffers.size(), drawBuffers.data()); + if (!drawBuffers.empty()) { + glDrawBuffers(drawBuffers.size(), drawBuffers.data()); + glClearColor(color.x, color.y, color.z, color.w); + glmask |= GL_COLOR_BUFFER_BIT; + + (void) CHECK_GL_ERROR(); + } + } else { glClearColor(color.x, color.y, color.z, color.w); glmask |= GL_COLOR_BUFFER_BIT; } From 9caf4407466c6291f1e6d3900a3cd9392ae6420e Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Tue, 4 Aug 2015 12:53:23 -0700 Subject: [PATCH 004/170] basic shader sort-of working (sip) --- tests/gpu-test/CMakeLists.txt | 2 + tests/gpu-test/src/gputest_shaders.h | 48 ++++ tests/gpu-test/src/main.cpp | 334 +++++++++++++++++++-------- 3 files changed, 287 insertions(+), 97 deletions(-) create mode 100644 tests/gpu-test/src/gputest_shaders.h diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 9f423e3645..c4aaf73eb8 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -1,6 +1,8 @@ set(TARGET_NAME gpu-test) +AUTOSCRIBE_SHADER_LIB(gpu) + # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") diff --git a/tests/gpu-test/src/gputest_shaders.h b/tests/gpu-test/src/gputest_shaders.h new file mode 100644 index 0000000000..911ae4b624 --- /dev/null +++ b/tests/gpu-test/src/gputest_shaders.h @@ -0,0 +1,48 @@ +// +// gputest_shaders.h +// hifi +// +// Created by Seiji Emery on 8/3/15. +// +// + +#ifndef hifi_gputest_shaders_h +#define hifi_gputest_shaders_h + +const std::string & basicVertexShader () { + static std::string src = R"( + +// attribute vec3 position; +// attribute vec3 normal; + + varying vec3 normal; + + void main (void) { +// gl_Position = gl_ModelViewProjectionMatrix * vec4(position.xyz, 1.0); + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + normal = gl_Normal; + } + + )"; + return src; +} + +const std::string & basicFragmentShader () { + static std::string src = R"( + #version 400 + + varying vec3 normal; + + void main(void) { + gl_FragColor.rgb = vec3(0.7, 0.2, 0.5) + gl_FragCoord.xyz * 0.2; + + vec3 diffuse = vec3(0.7, 0.2, 0.5); + vec3 light_normal = vec3(0.5, -0.5, 0.7); + + gl_FragColor.rgb = diffuse * dot(light_normal, normal); + } + + )"; + return src; +} +#endif diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index a4a68c1d84..a0dabc2516 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -38,13 +38,12 @@ #include #include #include - - #include - - #include +#include "gputest_shaders.h" + + class RateCounter { std::vector times; QElapsedTimer timer; @@ -81,15 +80,6 @@ public: } }; -const char * basicVS = -" varying vec3 pos; " -" void main(void) { " -" gl_Position.xyz = pos; " -" } "; -const char * basicFS = -" void main(void) { " -" gl_FragColor.xyz = vec3(0.7, 0.2, 0.5); " -" } "; const QString& getQmlDir() { static QString dir; @@ -102,6 +92,170 @@ const QString& getQmlDir() { return dir; } +#define MOVE_PARAM(name) decltype(name) && name + +struct BasicModel { + gpu::PipelinePointer pipeline; +// gpu::BufferPointer vertexBuffer; +// gpu::BufferPointer indexBuffer; +// gpu::BufferPointer normalBuffer; + + gpu::BufferView vertices; + gpu::BufferView normals; + gpu::BufferPointer indices; + + gpu::Stream::FormatPointer format; + + BasicModel (MOVE_PARAM(pipeline), MOVE_PARAM(vertices), MOVE_PARAM(normals), MOVE_PARAM(indices), MOVE_PARAM(format)) + : pipeline(pipeline), vertices(vertices), normals(normals), indices(indices), format(format) {} + +// BasicModel (gpu::PipelinePointer && pipeline, gpu::BufferPointer && buffer, gpu::Stream::FormatPointer && format) +// : pipeline(pipeline), buffer(buffer), format(format) {} +}; +typedef std::shared_ptr BasicModelPointer; +#undef MOVE_PARAM + +BasicModelPointer makeCube () { + // Axis-aligned cube, facing the user at +z + // coords == binary mapping of each index, with z inverted (front face faces camera, + // instead of away from the camera) + // + // -x,+y,-z ----------- +x,+y,-z + // ___--- | ___--- | + // -x,+y,+z --------- +x,+y,+z | + // | | | | + // | | | | + // | | | | + // | | | | + // | -x,-y,-z ------|---- +x,-y,-z + // | ___--- | ___---- + // -x,-y,+z --------- +x,-y,+z + // + float s = 1.0f; + const glm::vec3 raw_verts[8] = { + // x, y, z + { -s, -s, +s }, // 0b000 0x0 + { +s, -s, +s }, // 0b001 0x1 + { -s, +s, +s }, // 0b010 0x2 + { +s, +s, +s }, // 0b011 0x3 + { -s, -s, -s }, // 0b100 0x4 + { +s, -s, -s }, // 0b101 0x5 + { -s, +s, -s }, // 0b110 0x6 + { +s, +s, -s } // 0b111 0x7 + }; + const glm::vec3 raw_normals[6] = { + { 0.0f, 0.0f, +1.0f }, // x > 0: 1, 3, 5, 7 (N 0) + { 0.0f, 0.0f, -1.0f }, // x < 0: 0, 2, 4, 6 (N 1) + { 0.0f, +1.0f, 0.0f }, // y > 0: 2, 3, 6, 7 (N 2) + { 0.0f, -1.0f, 0.0f }, // y < 0: 0, 1, 4, 5 (N 3) + { +1.0f, 0.0f, 0.0f }, // z > 0: 0, 1, 2, 3 (N 4) + { -1.0f, 0.0f, 0.0f } // z < 0: 4, 5, 6, 7 (N 5) + }; + + const glm::vec3 cube_verts[24] = { + raw_verts[1], raw_verts[3], raw_verts[5], raw_verts[7], + raw_verts[0], raw_verts[2], raw_verts[4], raw_verts[6], + raw_verts[2], raw_verts[3], raw_verts[6], raw_verts[7], + raw_verts[0], raw_verts[1], raw_verts[4], raw_verts[5], + raw_verts[0], raw_verts[1], raw_verts[2], raw_verts[3], + raw_verts[4], raw_verts[5], raw_verts[6], raw_verts[7] + }; + const glm::vec3 cube_normals[24] = { + raw_normals[0], raw_normals[0], raw_normals[0], raw_normals[0], + raw_normals[1], raw_normals[1], raw_normals[1], raw_normals[1], + raw_normals[2], raw_normals[2], raw_normals[2], raw_normals[2], + raw_normals[3], raw_normals[3], raw_normals[3], raw_normals[3], + raw_normals[4], raw_normals[4], raw_normals[4], raw_normals[4], + raw_normals[5], raw_normals[5], raw_normals[5], raw_normals[5] + }; + + int16_t cube_indices_tris[36]; + for (int i = 0, k = 0; i < 36; k += 4) { + cube_indices_tris[i++] = k + 0; + cube_indices_tris[i++] = k + 3; + cube_indices_tris[i++] = k + 1; + cube_indices_tris[i++] = k + 0; + cube_indices_tris[i++] = k + 2; + cube_indices_tris[i++] = k + 3; + } + +// const int16_t cube_indices_tris[36] { +// 0, 3, 1, 0, 2, 3, +// }; + +// const glm::vec3 cube_normals[] = { +// { 0.0f, 0.0f, 1.0f }, +// { 0.0f, 0.0f, 1.0f }, +// { 0.0f, 0.0f, 1.0f }, +// { 0.0f, 0.0f, 1.0f }, +// { -1.0f, 0.0f, 0.0f }, +// { -1.0f, 0.0f, 0.0f }, +// { -1.0f, 0.0f, 0.0f }, +// { -1.0f, 0.0f, 0.0f }, +// }; +// const int16_t cube_indices[] = { +// 3, 1, 0, 2, 3, 0, +// 6, 2, 0, 4, 6, 0, +// }; + + gpu::Stream::FormatPointer format = std::make_shared(); + format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element::VEC3F_XYZ); + format->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, gpu::Element::VEC3F_XYZ); + + auto vertexBuffer = std::make_shared(24 * sizeof(glm::vec3), (gpu::Byte*)cube_verts); + auto normalBuffer = std::make_shared(24 * sizeof(glm::vec3), (gpu::Byte*)cube_normals); + gpu::BufferPointer indexBuffer = std::make_shared(36 * sizeof(int16_t), (gpu::Byte*)cube_indices_tris); + + auto positionElement = format->getAttributes().at(gpu::Stream::POSITION)._element; + auto normalElement = format->getAttributes().at(gpu::Stream::NORMAL)._element; + + gpu::BufferView vertexView { vertexBuffer, positionElement }; + gpu::BufferView normalView { normalBuffer, normalElement }; + + // Create shaders + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(basicVertexShader())); + auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(basicFragmentShader())); + auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); + + gpu::Shader::BindingSet bindings; + + if (!gpu::Shader::makeProgram(*shader, bindings)) { + printf("Could not compile shader\n"); + if (!vs) + printf("bad vertex shader\n"); + if (!fs) + printf("bad fragment shader\n"); + if (!shader) + printf("bad shader program\n"); + exit(-1); + } + + auto state = std::make_shared(); +// state->setAntialiasedLineEnable(true); + state->setMultisampleEnable(true); + state->setDepthTest({ true }); + auto pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); + + return std::make_shared( + std::move(pipeline), + std::move(vertexView), + std::move(normalView), + std::move(indexBuffer), + std::move(format) + ); +} + +void renderCube(gpu::Batch & batch, const BasicModel & cube) { + + batch.setPipeline(cube.pipeline); + batch.setInputFormat(cube.format); + batch.setInputBuffer(gpu::Stream::POSITION, cube.vertices); + batch.setInputBuffer(gpu::Stream::NORMAL, cube.normals); + batch.setIndexBuffer(gpu::INT16, cube.indices, 0); +// batch.drawIndexed(gpu::TRIANGLES, 12); + batch.draw(gpu::TRIANGLES, 24); +} + // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { Q_OBJECT @@ -111,9 +265,7 @@ class QTestWindow : public QWindow { gpu::ContextPointer _context; gpu::PipelinePointer _pipeline; - gpu::BufferPointer _buffer; - gpu::Stream::FormatPointer _format; - + BasicModelPointer _cubeModel; //TextRenderer* _textRenderer[4]; RateCounter fps; @@ -154,63 +306,10 @@ public: batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 }); _context->render(batch); - // Create default shaders + _cubeModel = makeCube(); - std::string vsSource (basicVS); - std::string fsSource (basicFS); - - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(vsSource)); - auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(fsSource)); - auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); - - gpu::Shader::BindingSet bindings; - if (!gpu::Shader::makeProgram(*shader, bindings)) { - printf("Could not compile shader"); - exit(-1); - } - -// auto shader = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - - auto state = std::make_shared(); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); - - float z = 10.0f; - const glm::vec3 vertices[] = { - { -1.0f, 1.0f, z }, - { 1.0f, -1.0f, z }, - { -1.0f, -1.0f, z } - }; - _buffer = std::make_shared(sizeof(vertices), (const gpu::Byte*)vertices); - _format = std::make_shared(); - _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - - -// { -// QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); -// logger->initialize(); // initializes in the current context, i.e. ctx -// logger->enableMessages(); -// connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { -// qDebug() << debugMessage; -// }); -// // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); -// } -// qDebug() << (const char*)glGetString(GL_VERSION); - - //_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); - //_textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, - // TextRenderer::SHADOW_EFFECT); - //_textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1, - // false, TextRenderer::OUTLINE_EFFECT); - //_textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24); - -// glEnable(GL_BLEND); -// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -// glClearColor(0.2f, 0.2f, 0.2f, 1); -// glDisable(GL_DEPTH_TEST); - - makeCurrent(); - - _context->syncCache(); +// makeCurrent(); +// _context->syncCache(); setFramePosition(QPoint(-1000, 0)); resize(QSize(800, 600)); @@ -257,41 +356,81 @@ void QTestWindow::draw() { gpu::Batch batch; static int frameNum = 0; frameNum++; - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, float(frameNum % 60)/60.0f, 0.5, 1.0 }); -// _context->render(batch); + float t = frameNum / 120.0f; + + float k = (frameNum % 120) / 120; + float ks = glm::sin(glm::pi() * 2.0f * k); + + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.1, 0.1, 0.1, 1.0 }); +// batch.clearDepthFramebuffer(-10000.0f); +// batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, float(frameNum % 60)/60.0f, 0.5, 1.0 }); + + batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); -//// batch.clear(); -// batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); -// - glm::quat cubeOrientation; -// - batch.setViewTransform(Transform()); - batch.setProjectionTransform(glm::mat4()); -// batch.setProjectionTransform(_viewFrustrum->getProjection()); - batch.setModelTransform(Transform().setRotation(cubeOrientation)); - batch.setPipeline(_pipeline); - batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); - batch.setInputFormat(_format); - batch.draw(gpu::TRIANGLES, 3); + + // camera at x: 5 * sin(t) + // y: 1, + // z: +10 + // obj at (0, 0, 0) + // camera is looking at obj (using glm::lookAt) + + glm::vec3 up { 0.0f, 1.0f, 0.0f }; + glm::vec3 unitscale { 1.0f }; + + float cube_angle = 0.0f; +// glm::vec3 cube_pos { +// 0.0f, +// 0.0f, +// 0.0f +// }; + glm::vec3 cube_pos { + 20.0f * cos(t * 5.0f), + 10.0f * sin(t * 2.5f) + 1.0f, + -15.0f + float(int(t * int(1e3)) % int(1e4)) / 1e3 + }; + + // float cube_angle = 360.0f * k * 1.25 + 120.0f * k * 0.1f; +// glm::quat cube_rotation = glm::angleAxis(glm::radians(cube_angle), up); + glm::quat cube_rotation; + Transform cube_transform { cube_rotation, unitscale, cube_pos }; + +// glm::vec3 cam_pos { 0.0f, 0.0f, -10.0f }; + glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, -10.0f }; + glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, cube_pos, up)); + cam_rotation.w = -cam_rotation.w; + Transform cam_transform { cam_rotation, unitscale, cam_pos }; + + float fov_degrees = 120.0f; +// float aspect_ratio = _size.height() / (_size.width() || 1.0f); + float aspect_ratio = 16.0f / 9.0f; + float near_clip = 0.1f; + float far_clip = 1000.0f; + auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); + + batch.setProjectionTransform(projection); + batch.setViewTransform(cam_transform); + batch.setModelTransform(cube_transform); + + batch.setModelTransform(Transform().setTranslation({ 20.0f * cos(t * 5.0f), 10.0f * sin(t * 2.5f + 1.0f), -15.0f + float(int(t * 1000) % 10000) / 1e3f})); +// batch.setPipeline(_pipeline); +// batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); +// batch.setInputFormat(_format); +// batch.draw(gpu::TRIANGLES, 3); + + renderCube(batch, *_cubeModel); _context->render(batch); // //// gpu::Stream::Format format; //// format.setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::Vec3, gpu::FLOAT, gpu::XYZ)); //// batch.setInputBuffer(gpu::Stream::POSITION, _trianglePosBuffer, ) -// -// -//// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -//// glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); -// -//// _context->swapBuffers(this); -//// glFinish(); -// + _qGlContext->swapBuffers(this); + // glFinish(); fps.increment(); if (fps.elapsed() >= 2.0f) { - qDebug() << "FPS: " << fps.rate(); // This prints out the frames per 2 secs (ie. half of the actual fps) bug...? + qDebug() << "FPS: " << fps.rate() * 2.0f; // This prints out the frames per 2 secs (ie. half of the actual fps) bug...? fps.reset(); } } @@ -300,7 +439,8 @@ int main(int argc, char** argv) { QGuiApplication app(argc, argv); QTestWindow window; QTimer timer; - timer.setInterval(1000 / 120.0f); +// timer.setInterval(1000 / 120.0f); + timer.setInterval(0); app.connect(&timer, &QTimer::timeout, &app, [&] { window.draw(); }); From 5ce84c15ae22317cb14cd0ddfd6125ebcf36506a Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Tue, 4 Aug 2015 16:27:07 -0700 Subject: [PATCH 005/170] updated shaders --- tests/gpu-test/src/gputest_shaders.h | 106 +++++++++++++++++++++++---- tests/gpu-test/src/main.cpp | 17 +++-- 2 files changed, 102 insertions(+), 21 deletions(-) diff --git a/tests/gpu-test/src/gputest_shaders.h b/tests/gpu-test/src/gputest_shaders.h index 911ae4b624..376ed3c3f3 100644 --- a/tests/gpu-test/src/gputest_shaders.h +++ b/tests/gpu-test/src/gputest_shaders.h @@ -9,18 +9,96 @@ #ifndef hifi_gputest_shaders_h #define hifi_gputest_shaders_h -const std::string & basicVertexShader () { + +const std::string & standardVertexShader() { static std::string src = R"( -// attribute vec3 position; -// attribute vec3 normal; + in vec4 inPosition; + in vec4 inNormal; + in vec4 inColor; + in vec4 inTexCoord0; + in vec4 inTangent; + in vec4 inSkinClusterIndex; + in vec4 inSkinClusterWeight; + in vec4 inTexCoord1; - varying vec3 normal; + struct TransformObject { + mat4 _model; + mat4 _modelInverse; + }; + + struct TransformCamera { + mat4 _view; + mat4 _viewInverse; + mat4 _projectionViewUntranslated; + mat4 _projection; + mat4 _projectionInverse; + vec4 _viewport; + }; + + uniform transformObjectBuffer { + TransformObject _object; + }; + TransformObject getTransformObject() { + return _object; + } + + uniform transformCameraBuffer { + TransformCamera _camera; + }; + TransformCamera getTransformCamera() { + return _camera; + } + + + const int MAX_TEXCOORDS = 2; + + uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; + + out vec4 _position; + out vec3 _normal; + out vec3 _color; + out vec2 _texCoord0; + + )"; + return src; +} + + + + + +const std::string & basicVertexShader () { + static std::string src = R"( + // Basic forward-rendered shading w/ a single directional light source. + + #version 410 + + // I/O + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec3 vertexNormal; + + out vec3 outColor; + + // Light info + uniform vec4 lightPosition; + uniform vec3 kd; // diffuse reflectivity + uniform vec3 ld; // light source intensity + + // Model transforms + uniform mat4 modelViewMatrix; + uniform mat3 normalMatrix; + uniform mat4 projectionMatrix; + uniform mat4 mvp; void main (void) { -// gl_Position = gl_ModelViewProjectionMatrix * vec4(position.xyz, 1.0); - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; - normal = gl_Normal; + vec3 norm = normalize(normalMatrix * vertexNormal); + vec4 eyePos = modelViewMatrix * vec4(vertexPosition, 0); + vec3 s = normalize(vec3(lightPosition - eyePos)); + + outColor = ld * kd * max(dot(s, norm), 0.0); + + gl_Position = mvp * vec4(vertexPosition, 1.0); } )"; @@ -29,17 +107,15 @@ const std::string & basicVertexShader () { const std::string & basicFragmentShader () { static std::string src = R"( - #version 400 + #version 410 - varying vec3 normal; + // Just pass interpolated color value along + in vec3 outColor; + + layout (location = 0) out vec4 fragColor; void main(void) { - gl_FragColor.rgb = vec3(0.7, 0.2, 0.5) + gl_FragCoord.xyz * 0.2; - - vec3 diffuse = vec3(0.7, 0.2, 0.5); - vec3 light_normal = vec3(0.5, -0.5, 0.7); - - gl_FragColor.rgb = diffuse * dot(light_normal, normal); + fragColor = vec4(outColor, 1.0); } )"; diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index a0dabc2516..9fdfc3917d 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -199,15 +199,19 @@ BasicModelPointer makeCube () { // }; gpu::Stream::FormatPointer format = std::make_shared(); - format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element::VEC3F_XYZ); - format->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, gpu::Element::VEC3F_XYZ); + + assert(gpu::Stream::POSITION == 0 && gpu::Stream::NORMAL == 1); + const int BUFFER_SLOT = 0; + + format->setAttribute(gpu::Stream::POSITION, BUFFER_SLOT, gpu::Element::VEC3F_XYZ); + format->setAttribute(gpu::Stream::NORMAL, BUFFER_SLOT, gpu::Element::VEC3F_XYZ); auto vertexBuffer = std::make_shared(24 * sizeof(glm::vec3), (gpu::Byte*)cube_verts); auto normalBuffer = std::make_shared(24 * sizeof(glm::vec3), (gpu::Byte*)cube_normals); gpu::BufferPointer indexBuffer = std::make_shared(36 * sizeof(int16_t), (gpu::Byte*)cube_indices_tris); auto positionElement = format->getAttributes().at(gpu::Stream::POSITION)._element; - auto normalElement = format->getAttributes().at(gpu::Stream::NORMAL)._element; + auto normalElement = format->getAttributes().at(gpu::Stream::NORMAL)._element; gpu::BufferView vertexView { vertexBuffer, positionElement }; gpu::BufferView normalView { normalBuffer, normalElement }; @@ -218,7 +222,8 @@ BasicModelPointer makeCube () { auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); gpu::Shader::BindingSet bindings; - + bindings.insert({ "lightPosition", 1 }); + if (!gpu::Shader::makeProgram(*shader, bindings)) { printf("Could not compile shader\n"); if (!vs) @@ -285,8 +290,8 @@ public: // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(16); format.setStencilBufferSize(8); - format.setVersion(4, 5); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + format.setVersion(4, 1); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); format.setOption(QSurfaceFormat::DebugContext); setFormat(format); From 91bfd07cd43319c2d30ed89ef9666e6e8cc62226 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 5 Aug 2015 12:35:52 -0700 Subject: [PATCH 006/170] use geometry cache --- tests/gpu-test/src/main.cpp | 233 ++++++++++++++++++++++++++---------- 1 file changed, 169 insertions(+), 64 deletions(-) diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 9fdfc3917d..86f0c86bf1 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include "gputest_shaders.h" @@ -249,7 +250,6 @@ BasicModelPointer makeCube () { std::move(format) ); } - void renderCube(gpu::Batch & batch, const BasicModel & cube) { batch.setPipeline(cube.pipeline); @@ -329,7 +329,6 @@ public: } protected: - void resizeEvent(QResizeEvent* ev) override { resizeWindow(ev->size()); } @@ -351,13 +350,30 @@ static const glm::vec3 COLORS[4] = { }; +void renderTestScene (gpu::Batch & batch) { + std::once_flag initFlag; + gpu::PipelinePointer pipeline { nullptr }; + + std::call_once(initFlag, [&](){ + + }); + + auto geometryCache = DependencyManager::get(); + + geometryCache->renderGrid(batch, 4, 4, { 0.2f, 0.3f, 0.7f, 1.0f }); + + + + + +} + + void QTestWindow::draw() { if (!isVisible()) { return; } - makeCurrent(); - gpu::Batch batch; static int frameNum = 0; frameNum++; @@ -365,74 +381,67 @@ void QTestWindow::draw() { float k = (frameNum % 120) / 120; float ks = glm::sin(glm::pi() * 2.0f * k); - - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.1, 0.1, 0.1, 1.0 }); -// batch.clearDepthFramebuffer(-10000.0f); -// batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, float(frameNum % 60)/60.0f, 0.5, 1.0 }); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.1, 0.1, 0.1, 1.0 }); batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); - - - // camera at x: 5 * sin(t) - // y: 1, - // z: +10 - // obj at (0, 0, 0) - // camera is looking at obj (using glm::lookAt) - - glm::vec3 up { 0.0f, 1.0f, 0.0f }; - glm::vec3 unitscale { 1.0f }; - - float cube_angle = 0.0f; + renderTestScene(batch); +// +// +// // camera at x: 5 * sin(t) +// // y: 1, +// // z: +10 +// // obj at (0, 0, 0) +// // camera is looking at obj (using glm::lookAt) +// +// glm::vec3 up { 0.0f, 1.0f, 0.0f }; +// glm::vec3 unitscale { 1.0f }; +// +// float cube_angle = 0.0f; +// // glm::vec3 cube_pos { +// // 0.0f, +// // 0.0f, +// // 0.0f +// // }; // glm::vec3 cube_pos { -// 0.0f, -// 0.0f, -// 0.0f +// 20.0f * cos(t * 5.0f), +// 10.0f * sin(t * 2.5f) + 1.0f, +// -15.0f + float(int(t * int(1e3)) % int(1e4)) / 1e3 // }; - glm::vec3 cube_pos { - 20.0f * cos(t * 5.0f), - 10.0f * sin(t * 2.5f) + 1.0f, - -15.0f + float(int(t * int(1e3)) % int(1e4)) / 1e3 - }; +// +// // float cube_angle = 360.0f * k * 1.25 + 120.0f * k * 0.1f; +// // glm::quat cube_rotation = glm::angleAxis(glm::radians(cube_angle), up); +// glm::quat cube_rotation; +// Transform cube_transform { cube_rotation, unitscale, cube_pos }; +// +// // glm::vec3 cam_pos { 0.0f, 0.0f, -10.0f }; +// glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, -10.0f }; +// glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, cube_pos, up)); +// cam_rotation.w = -cam_rotation.w; +// Transform cam_transform { cam_rotation, unitscale, cam_pos }; +// +// float fov_degrees = 120.0f; +// // float aspect_ratio = _size.height() / (_size.width() || 1.0f); +// float aspect_ratio = 16.0f / 9.0f; +// float near_clip = 0.1f; +// float far_clip = 1000.0f; +// auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); +// +// batch.setProjectionTransform(projection); +// batch.setViewTransform(cam_transform); +// batch.setModelTransform(cube_transform); +// +// batch.setModelTransform(Transform().setTranslation({ 20.0f * cos(t * 5.0f), 10.0f * sin(t * 2.5f + 1.0f), -15.0f + float(int(t * 1000) % 10000) / 1e3f})); +// // batch.setPipeline(_pipeline); +// // batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); +// // batch.setInputFormat(_format); +// // batch.draw(gpu::TRIANGLES, 3); +// +// renderCube(batch, *_cubeModel); - // float cube_angle = 360.0f * k * 1.25 + 120.0f * k * 0.1f; -// glm::quat cube_rotation = glm::angleAxis(glm::radians(cube_angle), up); - glm::quat cube_rotation; - Transform cube_transform { cube_rotation, unitscale, cube_pos }; - -// glm::vec3 cam_pos { 0.0f, 0.0f, -10.0f }; - glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, -10.0f }; - glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, cube_pos, up)); - cam_rotation.w = -cam_rotation.w; - Transform cam_transform { cam_rotation, unitscale, cam_pos }; - - float fov_degrees = 120.0f; -// float aspect_ratio = _size.height() / (_size.width() || 1.0f); - float aspect_ratio = 16.0f / 9.0f; - float near_clip = 0.1f; - float far_clip = 1000.0f; - auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); - - batch.setProjectionTransform(projection); - batch.setViewTransform(cam_transform); - batch.setModelTransform(cube_transform); - - batch.setModelTransform(Transform().setTranslation({ 20.0f * cos(t * 5.0f), 10.0f * sin(t * 2.5f + 1.0f), -15.0f + float(int(t * 1000) % 10000) / 1e3f})); -// batch.setPipeline(_pipeline); -// batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); -// batch.setInputFormat(_format); -// batch.draw(gpu::TRIANGLES, 3); - - renderCube(batch, *_cubeModel); _context->render(batch); -// -//// gpu::Stream::Format format; -//// format.setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::Vec3, gpu::FLOAT, gpu::XYZ)); -//// batch.setInputBuffer(gpu::Stream::POSITION, _trianglePosBuffer, ) - _qGlContext->swapBuffers(this); - // glFinish(); - + fps.increment(); if (fps.elapsed() >= 2.0f) { qDebug() << "FPS: " << fps.rate() * 2.0f; // This prints out the frames per 2 secs (ie. half of the actual fps) bug...? @@ -440,6 +449,102 @@ void QTestWindow::draw() { } } + + + + + +//void QTestWindow::draw() { +// if (!isVisible()) { +// return; +// } +// +// makeCurrent(); +// +// gpu::Batch batch; +// static int frameNum = 0; +// frameNum++; +// float t = frameNum / 120.0f; +// +// float k = (frameNum % 120) / 120; +// float ks = glm::sin(glm::pi() * 2.0f * k); +// +// batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.1, 0.1, 0.1, 1.0 }); +//// batch.clearDepthFramebuffer(-10000.0f); +//// batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, float(frameNum % 60)/60.0f, 0.5, 1.0 }); +// +// batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); +// +// +// +// // camera at x: 5 * sin(t) +// // y: 1, +// // z: +10 +// // obj at (0, 0, 0) +// // camera is looking at obj (using glm::lookAt) +// +// glm::vec3 up { 0.0f, 1.0f, 0.0f }; +// glm::vec3 unitscale { 1.0f }; +// +// float cube_angle = 0.0f; +//// glm::vec3 cube_pos { +//// 0.0f, +//// 0.0f, +//// 0.0f +//// }; +// glm::vec3 cube_pos { +// 20.0f * cos(t * 5.0f), +// 10.0f * sin(t * 2.5f) + 1.0f, +// -15.0f + float(int(t * int(1e3)) % int(1e4)) / 1e3 +// }; +// +// // float cube_angle = 360.0f * k * 1.25 + 120.0f * k * 0.1f; +//// glm::quat cube_rotation = glm::angleAxis(glm::radians(cube_angle), up); +// glm::quat cube_rotation; +// Transform cube_transform { cube_rotation, unitscale, cube_pos }; +// +//// glm::vec3 cam_pos { 0.0f, 0.0f, -10.0f }; +// glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, -10.0f }; +// glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, cube_pos, up)); +// cam_rotation.w = -cam_rotation.w; +// Transform cam_transform { cam_rotation, unitscale, cam_pos }; +// +// float fov_degrees = 120.0f; +//// float aspect_ratio = _size.height() / (_size.width() || 1.0f); +// float aspect_ratio = 16.0f / 9.0f; +// float near_clip = 0.1f; +// float far_clip = 1000.0f; +// auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); +// +// batch.setProjectionTransform(projection); +// batch.setViewTransform(cam_transform); +// batch.setModelTransform(cube_transform); +// +// batch.setModelTransform(Transform().setTranslation({ 20.0f * cos(t * 5.0f), 10.0f * sin(t * 2.5f + 1.0f), -15.0f + float(int(t * 1000) % 10000) / 1e3f})); +//// batch.setPipeline(_pipeline); +//// batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); +//// batch.setInputFormat(_format); +//// batch.draw(gpu::TRIANGLES, 3); +// +// renderCube(batch, *_cubeModel); +// +// DependencyManager::get()->renderGrid(batch, 4, 4, glm::vec4 { 0.3f, 0.3f, 0.3f, 1.0f }); +// _context->render(batch); +//// +////// gpu::Stream::Format format; +////// format.setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::Vec3, gpu::FLOAT, gpu::XYZ)); +////// batch.setInputBuffer(gpu::Stream::POSITION, _trianglePosBuffer, ) +// +// _qGlContext->swapBuffers(this); +// // glFinish(); +// +// fps.increment(); +// if (fps.elapsed() >= 2.0f) { +// qDebug() << "FPS: " << fps.rate() * 2.0f; // This prints out the frames per 2 secs (ie. half of the actual fps) bug...? +// fps.reset(); +// } +//} + int main(int argc, char** argv) { QGuiApplication app(argc, argv); QTestWindow window; From ba8a15b8931e01fb99dc965193131f61cd1591de Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 5 Aug 2015 20:02:22 -0700 Subject: [PATCH 007/170] Added primitives (geometryCache) --- tests/gpu-test/CMakeLists.txt | 4 +- tests/gpu-test/src/gputest_simple_frag.h | 105 ++++++++++++++++++ tests/gpu-test/src/gputest_simple_vert.h | 113 +++++++++++++++++++ tests/gpu-test/src/main.cpp | 132 ++++++++++++----------- tests/gpu-test/src/simple.slf | 29 +++++ tests/gpu-test/src/simple.slv | 36 +++++++ 6 files changed, 357 insertions(+), 62 deletions(-) create mode 100644 tests/gpu-test/src/gputest_simple_frag.h create mode 100644 tests/gpu-test/src/gputest_simple_vert.h create mode 100644 tests/gpu-test/src/simple.slf create mode 100644 tests/gpu-test/src/simple.slv diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index c4aaf73eb8..faedca537e 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME gpu-test) -AUTOSCRIBE_SHADER_LIB(gpu) +AUTOSCRIBE_SHADER_LIB(gpu model render gpu-test) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL) @@ -10,6 +10,6 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") #include_oglplus() # link in the shared libraries -link_hifi_libraries(render-utils gpu shared) +link_hifi_libraries(render-utils gpu shared networking fbx model animation) copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tests/gpu-test/src/gputest_simple_frag.h b/tests/gpu-test/src/gputest_simple_frag.h new file mode 100644 index 0000000000..324e26b082 --- /dev/null +++ b/tests/gpu-test/src/gputest_simple_frag.h @@ -0,0 +1,105 @@ +// File generated by Scribe Wed Aug 5 16:50:24 2015 +#ifndef scribe_simple_frag_h +#define scribe_simple_frag_h + +const char simple_frag[] = R"SCRIBE(#version 410 core +// Generated on Wed Aug 5 16:50:24 2015 +// +// simple.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/15/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 +// + +layout(location = 0) out vec4 _fragColor0; +layout(location = 1) out vec4 _fragColor1; +layout(location = 2) out vec4 _fragColor2; + +// the glow intensity +uniform float glowIntensity; + +// the alpha threshold +uniform float alphaThreshold; + +float evalOpaqueFinalAlpha(float alpha, float mapAlpha) { + return mix(alpha * glowIntensity, 1.0 - alpha * glowIntensity, step(mapAlpha, alphaThreshold)); +} + +const vec3 DEFAULT_SPECULAR = vec3(0.1); +const float DEFAULT_SHININESS = 10; + +void packDeferredFragment(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { + if (alpha != glowIntensity) { + discard; + } + _fragColor0 = vec4(diffuse.rgb, alpha); + _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); + _fragColor2 = vec4(specular, shininess / 128.0); +} + +void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess, vec3 emissive) { + if (alpha != glowIntensity) { + discard; + } + + _fragColor0 = vec4(diffuse.rgb, alpha); + //_fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); + _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5); + _fragColor2 = vec4(emissive, shininess / 128.0); +} + +void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { + if (alpha <= alphaThreshold) { + discard; + } + + _fragColor0 = vec4(diffuse.rgb, alpha); + // _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); + // _fragColor2 = vec4(specular, shininess / 128.0); +} + + + + + +struct Material { + vec4 _diffuse; + vec4 _specular; + vec4 _emissive; + vec4 _spare; +}; + +uniform materialBuffer { + Material _mat; +}; + +Material getMaterial() { + return _mat; +} + +float getMaterialOpacity(Material m) { return m._diffuse.a; } +vec3 getMaterialDiffuse(Material m) { return m._diffuse.rgb; } +vec3 getMaterialSpecular(Material m) { return m._specular.rgb; } +float getMaterialShininess(Material m) { return m._specular.a; } + + +// the interpolated normal +in vec3 _normal; +in vec3 _color; + +void main(void) { + Material material = getMaterial(); + packDeferredFragment( + normalize(_normal.xyz), + glowIntensity, + _color.rgb, + DEFAULT_SPECULAR, DEFAULT_SHININESS); +} + +)SCRIBE"; + +#endif diff --git a/tests/gpu-test/src/gputest_simple_vert.h b/tests/gpu-test/src/gputest_simple_vert.h new file mode 100644 index 0000000000..419c91b7c2 --- /dev/null +++ b/tests/gpu-test/src/gputest_simple_vert.h @@ -0,0 +1,113 @@ +// File generated by Scribe Wed Aug 5 16:50:23 2015 +#ifndef scribe_simple_vert_h +#define scribe_simple_vert_h + +const char simple_vert[] = R"SCRIBE(#version 410 core +// Generated on Wed Aug 5 16:50:23 2015 +// +// simple.vert +// vertex shader +// +// Created by Andrzej Kapolka on 9/15/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 +// + +in vec4 inPosition; +in vec4 inNormal; +in vec4 inColor; +in vec4 inTexCoord0; +in vec4 inTangent; +in vec4 inSkinClusterIndex; +in vec4 inSkinClusterWeight; +in vec4 inTexCoord1; + + + + + + + + + + + + + + + + + + + + + + +struct TransformObject { + mat4 _model; + mat4 _modelInverse; +}; + +struct TransformCamera { + mat4 _view; + mat4 _viewInverse; + mat4 _projectionViewUntranslated; + mat4 _projection; + mat4 _projectionInverse; + vec4 _viewport; +}; + +uniform transformObjectBuffer { + TransformObject _object; +}; +TransformObject getTransformObject() { + return _object; +} + +uniform transformCameraBuffer { + TransformCamera _camera; +}; +TransformCamera getTransformCamera() { + return _camera; +} + + +// the interpolated normal + +out vec3 _normal; +out vec3 _color; +out vec2 _texCoord0; + +void main(void) { + _color = inColor.rgb; + _texCoord0 = inTexCoord0.st; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + + + { // transformModelToClipPos + vec4 _eyepos = (obj._model * inPosition) + vec4(-inPosition.w * cam._viewInverse[3].xyz, 0.0); + gl_Position = cam._projectionViewUntranslated * _eyepos; + } + + + { // transformModelToEyeDir + vec3 mr0 = vec3(obj._modelInverse[0].x, obj._modelInverse[1].x, obj._modelInverse[2].x); + vec3 mr1 = vec3(obj._modelInverse[0].y, obj._modelInverse[1].y, obj._modelInverse[2].y); + vec3 mr2 = vec3(obj._modelInverse[0].z, obj._modelInverse[1].z, obj._modelInverse[2].z); + + vec3 mvc0 = vec3(dot(cam._viewInverse[0].xyz, mr0), dot(cam._viewInverse[0].xyz, mr1), dot(cam._viewInverse[0].xyz, mr2)); + vec3 mvc1 = vec3(dot(cam._viewInverse[1].xyz, mr0), dot(cam._viewInverse[1].xyz, mr1), dot(cam._viewInverse[1].xyz, mr2)); + vec3 mvc2 = vec3(dot(cam._viewInverse[2].xyz, mr0), dot(cam._viewInverse[2].xyz, mr1), dot(cam._viewInverse[2].xyz, mr2)); + + _normal = vec3(dot(mvc0, inNormal.xyz), dot(mvc1, inNormal.xyz), dot(mvc2, inNormal.xyz)); + } + +} +)SCRIBE"; + +#endif diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 86f0c86bf1..126c9dd952 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -43,7 +43,8 @@ #include #include "gputest_shaders.h" - +#include "gputest_simple_frag.h" +#include "gputest_simple_vert.h" class RateCounter { std::vector times; @@ -313,6 +314,8 @@ public: _cubeModel = makeCube(); + DependencyManager::set(); + // makeCurrent(); // _context->syncCache(); @@ -355,19 +358,52 @@ void renderTestScene (gpu::Batch & batch) { gpu::PipelinePointer pipeline { nullptr }; std::call_once(initFlag, [&](){ - + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(simple_vert))); + auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_frag))); + auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); + + gpu::Shader::BindingSet bindings; + if (!gpu::Shader::makeProgram(*shader, bindings)) { + printf("Could not compile shader\n"); + if (!vs) + printf("bad vertex shader\n"); + if (!fs) + printf("bad fragment shader\n"); + if (!shader) + printf("bad shader program\n"); + exit(-1); + } + auto state = std::make_shared(); + state->setMultisampleEnable(true); + state->setDepthTest({ true }); + pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); }); - + batch.setPipeline(pipeline); auto geometryCache = DependencyManager::get(); - geometryCache->renderGrid(batch, 4, 4, { 0.2f, 0.3f, 0.7f, 1.0f }); + float scale = 80.0f; + + batch.setModelTransform(Transform(glm::angleAxis(40.0f, glm::vec3{ 0.0f, 1.0f, 0.0f}), glm::vec3 { scale }, glm::vec3 { -50.0f, -50.0f, 0.0f })); +// geometryCache->renderGrid(batch, 800, 800, { 0.35f, 0.25f, 0.15f, 1.0f }); +// geometryCache->renderGrid(batch, 200, 200, { 0.4f, 0.4f, 0.9f, 1.0f }); - - - -} + batch.setModelTransform(Transform()); + for (int i = 0; i < 100; ++i) { + geometryCache->renderLine(batch, { -100.0f, -1.0f, -50.0f + float(i) }, { 100.0f, -1.0f, -50.0f + float(i) }, { 0.35f, 0.25f, 0.15f, 1.0f }); + } + for (int i = 0; i < 100; ++i) { + geometryCache->renderLine(batch, { -50.0f + float(i), -1.0f, -100.0f}, { -50.0f + float(i), -1.0f, 100.0f }, { 0.15f, 0.25f, 0.35f, 1.0f }); + } + + geometryCache->renderUnitCube(batch); + geometryCache->renderWireCube(batch, 1.0f, { 0.4f, 0.4f, 0.7f, 1.0f }); + + batch.setModelTransform(Transform().setTranslation({ 1.5f, -0.5f, -0.5f })); + geometryCache->renderSphere(batch, 0.5f, 50, 50, { 0.8f, 0.25f, 0.25f }); +// geometryCache->renderWireCube(batch, 1.0f, { 0.2f, 0.2f, 0.2f, 1.0f }); +} void QTestWindow::draw() { if (!isVisible()) { @@ -383,61 +419,37 @@ void QTestWindow::draw() { float ks = glm::sin(glm::pi() * 2.0f * k); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.1, 0.1, 0.1, 1.0 }); + batch.clearDepthFramebuffer(100.0f); batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); +// glm::vec3 camera_focus { 5.0f * cos(t * 0.1f), 0.0f, 0.0f }; + glm::vec3 camera_focus { 0.0f, 0.0f, 0.0f }; + + glm::vec3 unitscale { 1.0f }; + glm::vec3 up { 0.0f, 1.0f, 0.0f }; + glm::vec3 cam_pos { 1.5f * sin(t), 0.0f, 2.0f }; +// glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, 10.0f }; +// glm::quat cam_rotation; + glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, camera_focus, up)); + cam_rotation.w = -cam_rotation.w; + Transform cam_transform { cam_rotation, unitscale, cam_pos }; + + + float fov_degrees = 60; +// float aspect_ratio = _size.height() / (_size.width() || 1.0f); + float aspect_ratio = (float)_size.width() / _size.height(); +// float aspect_ratio = 16.0f / 9.0f; + float near_clip = 0.1f; + float far_clip = 1000.0f; + auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); + + batch.setProjectionTransform(projection); + + +// batch.setViewTransform(Transform().setTranslation({ 1.5f * sin(t), 0.5f, 1.0f })); + batch.setViewTransform(cam_transform); + renderTestScene(batch); -// -// -// // camera at x: 5 * sin(t) -// // y: 1, -// // z: +10 -// // obj at (0, 0, 0) -// // camera is looking at obj (using glm::lookAt) -// -// glm::vec3 up { 0.0f, 1.0f, 0.0f }; -// glm::vec3 unitscale { 1.0f }; -// -// float cube_angle = 0.0f; -// // glm::vec3 cube_pos { -// // 0.0f, -// // 0.0f, -// // 0.0f -// // }; -// glm::vec3 cube_pos { -// 20.0f * cos(t * 5.0f), -// 10.0f * sin(t * 2.5f) + 1.0f, -// -15.0f + float(int(t * int(1e3)) % int(1e4)) / 1e3 -// }; -// -// // float cube_angle = 360.0f * k * 1.25 + 120.0f * k * 0.1f; -// // glm::quat cube_rotation = glm::angleAxis(glm::radians(cube_angle), up); -// glm::quat cube_rotation; -// Transform cube_transform { cube_rotation, unitscale, cube_pos }; -// -// // glm::vec3 cam_pos { 0.0f, 0.0f, -10.0f }; -// glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, -10.0f }; -// glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, cube_pos, up)); -// cam_rotation.w = -cam_rotation.w; -// Transform cam_transform { cam_rotation, unitscale, cam_pos }; -// -// float fov_degrees = 120.0f; -// // float aspect_ratio = _size.height() / (_size.width() || 1.0f); -// float aspect_ratio = 16.0f / 9.0f; -// float near_clip = 0.1f; -// float far_clip = 1000.0f; -// auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); -// -// batch.setProjectionTransform(projection); -// batch.setViewTransform(cam_transform); -// batch.setModelTransform(cube_transform); -// -// batch.setModelTransform(Transform().setTranslation({ 20.0f * cos(t * 5.0f), 10.0f * sin(t * 2.5f + 1.0f), -15.0f + float(int(t * 1000) % 10000) / 1e3f})); -// // batch.setPipeline(_pipeline); -// // batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); -// // batch.setInputFormat(_format); -// // batch.draw(gpu::TRIANGLES, 3); -// -// renderCube(batch, *_cubeModel); _context->render(batch); _qGlContext->swapBuffers(this); diff --git a/tests/gpu-test/src/simple.slf b/tests/gpu-test/src/simple.slf new file mode 100644 index 0000000000..31d33a73e4 --- /dev/null +++ b/tests/gpu-test/src/simple.slf @@ -0,0 +1,29 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/15/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include DeferredBufferWrite.slh@> +<@include model/Material.slh@> + +// the interpolated normal +in vec3 _normal; +in vec3 _color; + +void main(void) { + Material material = getMaterial(); + packDeferredFragment( + normalize(_normal.xyz), + glowIntensity, + _color.rgb, + DEFAULT_SPECULAR, DEFAULT_SHININESS); +} diff --git a/tests/gpu-test/src/simple.slv b/tests/gpu-test/src/simple.slv new file mode 100644 index 0000000000..99f404eaec --- /dev/null +++ b/tests/gpu-test/src/simple.slv @@ -0,0 +1,36 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple.vert +// vertex shader +// +// Created by Andrzej Kapolka on 9/15/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +// the interpolated normal + +out vec3 _normal; +out vec3 _color; +out vec2 _texCoord0; + +void main(void) { + _color = inColor.rgb; + _texCoord0 = inTexCoord0.st; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> +} \ No newline at end of file From dbf94d673be4b128ce510d45fcada3c27b1567e5 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Fri, 7 Aug 2015 12:01:58 -0700 Subject: [PATCH 008/170] Cleanup --- tests/gpu-test/src/main.cpp | 335 ++++++++++-------------------------- 1 file changed, 89 insertions(+), 246 deletions(-) diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 126c9dd952..7cc6adbd50 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -24,16 +24,9 @@ #include #include #include -//#include - #include -//#include #include -//#include -//#include -//#include -//#include #include #include #include @@ -82,18 +75,6 @@ public: } }; - -const QString& getQmlDir() { - static QString dir; - if (dir.isEmpty()) { - QDir path(__FILE__); - path.cdUp(); - dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/")) + "/"; - qDebug() << "Qml Path: " << dir; - } - return dir; -} - #define MOVE_PARAM(name) decltype(name) && name struct BasicModel { @@ -262,7 +243,21 @@ void renderCube(gpu::Batch & batch, const BasicModel & cube) { batch.draw(gpu::TRIANGLES, 24); } -// Create a simple OpenGL window that renders text in various ways + +gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(vertexShaderSrc)); + auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSrc)); + auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); + if (!gpu::Shader::makeProgram(*shader, bindings)) { + printf("Could not compile shader\n"); + exit(-1); + } + return shader; +} + + +// Creates an OpenGL window that renders a simple unlit scene using the gpu library and GeometryCache +// Should eventually get refactored into something that supports multiple gpu backends. class QTestWindow : public QWindow { Q_OBJECT @@ -271,9 +266,10 @@ class QTestWindow : public QWindow { gpu::ContextPointer _context; gpu::PipelinePointer _pipeline; - BasicModelPointer _cubeModel; - //TextRenderer* _textRenderer[4]; + glm::mat4 _projectionMatrix; +// BasicModelPointer _cubeModel; RateCounter fps; + QTime _time; protected: void renderText(); @@ -307,26 +303,88 @@ public: gpu::Context::init(); _context = std::make_shared(); + auto shader = makeShader(simple_vert, simple_frag, gpu::Shader::BindingSet {}); + auto state = std::make_shared(); + state->setMultisampleEnable(true); + state->setDepthTest(gpu::State::DepthTest { true }); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); + + // Clear screen gpu::Batch batch; batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 }); _context->render(batch); - _cubeModel = makeCube(); +// _cubeModel = makeCube(); DependencyManager::set(); - -// makeCurrent(); -// _context->syncCache(); setFramePosition(QPoint(-1000, 0)); resize(QSize(800, 600)); + + _time.start(); } virtual ~QTestWindow() { } - void draw(); + void draw() { + if (!isVisible()) { + return; + } + makeCurrent(); + + gpu::Batch batch; + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.0f, 0.0f, 1.0f }); + batch.clearDepthFramebuffer(1e4); + batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); + batch.setProjectionTransform(_projectionMatrix); + + double t = _time.elapsed() * 1e-3; + glm::vec3 unitscale { 1.0f }; + glm::vec3 up { 0.0f, 1.0f, 0.0f }; + + glm::vec3 cam_pos { 1.5f * sin(t), 0.0f, 2.0f }; +// glm::vec3 camera_focus { 5.0f * cos(t * 0.1f), 0.0f, 0.0f }; + glm::vec3 camera_focus { 0.0f, 0.0f, 0.0f }; + glm::quat cam_rotation; + // glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, camera_focus, up)); + // cam_rotation.w = -cam_rotation.w; + // printf("cam rotation: %f %f %f %f\n", cam_rotation.x, cam_rotation.y, cam_rotation.z, cam_rotation.w); + Transform cam_transform { cam_rotation, unitscale, cam_pos }; + + batch.setViewTransform(cam_transform); + batch.setPipeline(_pipeline); + + auto geometryCache = DependencyManager::get(); + + // Render grid on xz plane (not the optimal way to do things, but w/e) + // Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only + batch.setModelTransform(Transform()); + for (int i = 0; i < 100; ++i) { + geometryCache->renderLine(batch, { -100.0f, -1.0f, -50.0f + float(i) }, { 100.0f, -1.0f, -50.0f + float(i) }, { 0.35f, 0.25f, 0.15f, 1.0f }); + } + for (int i = 0; i < 100; ++i) { + geometryCache->renderLine(batch, { -50.0f + float(i), -1.0f, -100.0f}, { -50.0f + float(i), -1.0f, 100.0f }, { 0.15f, 0.25f, 0.35f, 1.0f }); + } + + // Render unlit cube + sphere + geometryCache->renderUnitCube(batch); + geometryCache->renderWireCube(batch, 1.0f, { 0.4f, 0.4f, 0.7f, 1.0f }); + + batch.setModelTransform(Transform().setTranslation({ 1.5f, -0.5f, -0.5f })); + geometryCache->renderSphere(batch, 0.5f, 50, 50, { 0.8f, 0.25f, 0.25f }); + + _context->render(batch); + _qGlContext->swapBuffers(this); + + fps.increment(); + if (fps.elapsed() >= 0.5f) { + qDebug() << "FPS: " << fps.rate(); + fps.reset(); + } + } + void makeCurrent() { _qGlContext->makeCurrent(this); } @@ -334,234 +392,19 @@ public: protected: void resizeEvent(QResizeEvent* ev) override { resizeWindow(ev->size()); - } -}; - -#ifndef SERIF_FONT_FAMILY -#define SERIF_FONT_FAMILY "Times New Roman" -#endif - -//static const wchar_t* EXAMPLE_TEXT = L"Hello"; -//static const wchar_t* EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; -static const glm::uvec2 QUAD_OFFSET(10, 10); - -static const glm::vec3 COLORS[4] = { - { 1.0, 1.0, 1.0 }, - { 0.5, 1.0, 0.5 }, - { 1.0, 0.5, 0.5 }, - { 0.5, 0.5, 1.0 } -}; - - -void renderTestScene (gpu::Batch & batch) { - std::once_flag initFlag; - gpu::PipelinePointer pipeline { nullptr }; - - std::call_once(initFlag, [&](){ - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(simple_vert))); - auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_frag))); - auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); - gpu::Shader::BindingSet bindings; - if (!gpu::Shader::makeProgram(*shader, bindings)) { - printf("Could not compile shader\n"); - if (!vs) - printf("bad vertex shader\n"); - if (!fs) - printf("bad fragment shader\n"); - if (!shader) - printf("bad shader program\n"); - exit(-1); - } - auto state = std::make_shared(); - state->setMultisampleEnable(true); - state->setDepthTest({ true }); - pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); - }); - batch.setPipeline(pipeline); - auto geometryCache = DependencyManager::get(); - - float scale = 80.0f; - - batch.setModelTransform(Transform(glm::angleAxis(40.0f, glm::vec3{ 0.0f, 1.0f, 0.0f}), glm::vec3 { scale }, glm::vec3 { -50.0f, -50.0f, 0.0f })); -// geometryCache->renderGrid(batch, 800, 800, { 0.35f, 0.25f, 0.15f, 1.0f }); -// geometryCache->renderGrid(batch, 200, 200, { 0.4f, 0.4f, 0.9f, 1.0f }); - - - batch.setModelTransform(Transform()); - - for (int i = 0; i < 100; ++i) { - geometryCache->renderLine(batch, { -100.0f, -1.0f, -50.0f + float(i) }, { 100.0f, -1.0f, -50.0f + float(i) }, { 0.35f, 0.25f, 0.15f, 1.0f }); - } - for (int i = 0; i < 100; ++i) { - geometryCache->renderLine(batch, { -50.0f + float(i), -1.0f, -100.0f}, { -50.0f + float(i), -1.0f, 100.0f }, { 0.15f, 0.25f, 0.35f, 1.0f }); - } - - geometryCache->renderUnitCube(batch); - geometryCache->renderWireCube(batch, 1.0f, { 0.4f, 0.4f, 0.7f, 1.0f }); - - batch.setModelTransform(Transform().setTranslation({ 1.5f, -0.5f, -0.5f })); - geometryCache->renderSphere(batch, 0.5f, 50, 50, { 0.8f, 0.25f, 0.25f }); -// geometryCache->renderWireCube(batch, 1.0f, { 0.2f, 0.2f, 0.2f, 1.0f }); -} - -void QTestWindow::draw() { - if (!isVisible()) { - return; - } - makeCurrent(); - gpu::Batch batch; - static int frameNum = 0; - frameNum++; - float t = frameNum / 120.0f; - - float k = (frameNum % 120) / 120; - float ks = glm::sin(glm::pi() * 2.0f * k); - - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.1, 0.1, 0.1, 1.0 }); - batch.clearDepthFramebuffer(100.0f); - batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); - -// glm::vec3 camera_focus { 5.0f * cos(t * 0.1f), 0.0f, 0.0f }; - glm::vec3 camera_focus { 0.0f, 0.0f, 0.0f }; - - glm::vec3 unitscale { 1.0f }; - glm::vec3 up { 0.0f, 1.0f, 0.0f }; - glm::vec3 cam_pos { 1.5f * sin(t), 0.0f, 2.0f }; -// glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, 10.0f }; -// glm::quat cam_rotation; - glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, camera_focus, up)); - cam_rotation.w = -cam_rotation.w; - Transform cam_transform { cam_rotation, unitscale, cam_pos }; - - - float fov_degrees = 60; -// float aspect_ratio = _size.height() / (_size.width() || 1.0f); - float aspect_ratio = (float)_size.width() / _size.height(); -// float aspect_ratio = 16.0f / 9.0f; + float fov_degrees = 60.0f; + float aspect_ratio = (float)_size.width() / _size.height(); float near_clip = 0.1f; float far_clip = 1000.0f; - auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); - - batch.setProjectionTransform(projection); - - -// batch.setViewTransform(Transform().setTranslation({ 1.5f * sin(t), 0.5f, 1.0f })); - batch.setViewTransform(cam_transform); - - renderTestScene(batch); - - _context->render(batch); - _qGlContext->swapBuffers(this); - - fps.increment(); - if (fps.elapsed() >= 2.0f) { - qDebug() << "FPS: " << fps.rate() * 2.0f; // This prints out the frames per 2 secs (ie. half of the actual fps) bug...? - fps.reset(); + _projectionMatrix = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); } -} - - - - - - -//void QTestWindow::draw() { -// if (!isVisible()) { -// return; -// } -// -// makeCurrent(); -// -// gpu::Batch batch; -// static int frameNum = 0; -// frameNum++; -// float t = frameNum / 120.0f; -// -// float k = (frameNum % 120) / 120; -// float ks = glm::sin(glm::pi() * 2.0f * k); -// -// batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.1, 0.1, 0.1, 1.0 }); -//// batch.clearDepthFramebuffer(-10000.0f); -//// batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, float(frameNum % 60)/60.0f, 0.5, 1.0 }); -// -// batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); -// -// -// -// // camera at x: 5 * sin(t) -// // y: 1, -// // z: +10 -// // obj at (0, 0, 0) -// // camera is looking at obj (using glm::lookAt) -// -// glm::vec3 up { 0.0f, 1.0f, 0.0f }; -// glm::vec3 unitscale { 1.0f }; -// -// float cube_angle = 0.0f; -//// glm::vec3 cube_pos { -//// 0.0f, -//// 0.0f, -//// 0.0f -//// }; -// glm::vec3 cube_pos { -// 20.0f * cos(t * 5.0f), -// 10.0f * sin(t * 2.5f) + 1.0f, -// -15.0f + float(int(t * int(1e3)) % int(1e4)) / 1e3 -// }; -// -// // float cube_angle = 360.0f * k * 1.25 + 120.0f * k * 0.1f; -//// glm::quat cube_rotation = glm::angleAxis(glm::radians(cube_angle), up); -// glm::quat cube_rotation; -// Transform cube_transform { cube_rotation, unitscale, cube_pos }; -// -//// glm::vec3 cam_pos { 0.0f, 0.0f, -10.0f }; -// glm::vec3 cam_pos { 5.0f * sin(t * 0.1f), 1.0f, -10.0f }; -// glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, cube_pos, up)); -// cam_rotation.w = -cam_rotation.w; -// Transform cam_transform { cam_rotation, unitscale, cam_pos }; -// -// float fov_degrees = 120.0f; -//// float aspect_ratio = _size.height() / (_size.width() || 1.0f); -// float aspect_ratio = 16.0f / 9.0f; -// float near_clip = 0.1f; -// float far_clip = 1000.0f; -// auto projection = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); -// -// batch.setProjectionTransform(projection); -// batch.setViewTransform(cam_transform); -// batch.setModelTransform(cube_transform); -// -// batch.setModelTransform(Transform().setTranslation({ 20.0f * cos(t * 5.0f), 10.0f * sin(t * 2.5f + 1.0f), -15.0f + float(int(t * 1000) % 10000) / 1e3f})); -//// batch.setPipeline(_pipeline); -//// batch.setInputBuffer(gpu::Stream::POSITION, _buffer, 0, 3); -//// batch.setInputFormat(_format); -//// batch.draw(gpu::TRIANGLES, 3); -// -// renderCube(batch, *_cubeModel); -// -// DependencyManager::get()->renderGrid(batch, 4, 4, glm::vec4 { 0.3f, 0.3f, 0.3f, 1.0f }); -// _context->render(batch); -//// -////// gpu::Stream::Format format; -////// format.setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::Vec3, gpu::FLOAT, gpu::XYZ)); -////// batch.setInputBuffer(gpu::Stream::POSITION, _trianglePosBuffer, ) -// -// _qGlContext->swapBuffers(this); -// // glFinish(); -// -// fps.increment(); -// if (fps.elapsed() >= 2.0f) { -// qDebug() << "FPS: " << fps.rate() * 2.0f; // This prints out the frames per 2 secs (ie. half of the actual fps) bug...? -// fps.reset(); -// } -//} +}; int main(int argc, char** argv) { QGuiApplication app(argc, argv); QTestWindow window; QTimer timer; -// timer.setInterval(1000 / 120.0f); timer.setInterval(0); app.connect(&timer, &QTimer::timeout, &app, [&] { window.draw(); From 25266b986c07cfdcfa4e5d48315641932ae09d16 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 7 Aug 2015 21:43:14 +0200 Subject: [PATCH 009/170] added basic implementation of WebSocket class ( https://developer.mozilla.org/en-US/docs/Web/API/WebSocket ) using QWebSocket --- interface/CMakeLists.txt | 2 +- libraries/script-engine/CMakeLists.txt | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 4 + .../script-engine/src/WebSocketClass.cpp | 82 ++++++++++++ libraries/script-engine/src/WebSocketClass.h | 118 ++++++++++++++++++ 5 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 libraries/script-engine/src/WebSocketClass.cpp create mode 100644 libraries/script-engine/src/WebSocketClass.h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f1ef38ade9..f1bcbf4c47 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -60,7 +60,7 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets) +find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets) # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 99d9149c3a..139b99e426 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME script-engine) # use setup_hifi_library macro to setup our project and link appropriate Qt modules -setup_hifi_library(Gui Network Script Widgets) +setup_hifi_library(Gui Network Script WebSockets Widgets) add_dependency_external_projects(glm) find_package(GLM REQUIRED) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d5e727657c..afbf3eaf38 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -39,6 +39,7 @@ #include "ScriptEngine.h" #include "TypedArrays.h" #include "XMLHttpRequestClass.h" +#include "WebSocketClass.h" #include "SceneScriptingInterface.h" @@ -344,6 +345,9 @@ void ScriptEngine::init() { QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor); globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); + QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor); + globalObject().setProperty("WebSocket", webSocketConstructorValue); + QScriptValue printConstructorValue = newFunction(debugPrint); globalObject().setProperty("print", printConstructorValue); diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp new file mode 100644 index 0000000000..1b8d305b16 --- /dev/null +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -0,0 +1,82 @@ +// +// WebSocketClass.cpp +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/4/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// This class is an implementation of the WebSocket object for scripting use. It provides a near-complete implementation +// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngine.h" +#include "WebSocketClass.h" + +WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) : + _engine(engine) +{ + connect(&_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose); + connect(&_webSocket, SIGNAL(error()), this, SLOT(handleOnError())); + connect(&_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage); + connect(&_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen); + _webSocket.open(url); +} + +QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { + QString url; + QScriptValue callee = context->callee(); + if (context->argumentCount() == 1) { + url = context->argument(0).toString(); + } + return engine->newQObject(new WebSocketClass(engine, url)); +} + +WebSocketClass::~WebSocketClass() { + +} + +void WebSocketClass::send(QScriptValue message) { + _webSocket.sendTextMessage(message.toString()); +} + +void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reason) { + _webSocket.close(closeCode, reason); +} + +void WebSocketClass::handleOnClose() { + if (_onCloseEvent.isFunction()) { + //QScriptValueList args; + //args << ("received: " + message); + _onCloseEvent.call();//(QScriptValue(), args); + } +} + +void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) { + if (_onErrorEvent.isFunction()) { + // QScriptValueList args; + //args << ("received: " + message); + _onErrorEvent.call();/// QScriptValue(), args); + } +} + +void WebSocketClass::handleOnMessage(const QString& message) { + if (_onMessageEvent.isFunction()) { + + QScriptValueList args; + QScriptValue arg = _engine->newObject(); + arg.setProperty("data", message); + args << arg; + _onMessageEvent.call(QScriptValue(), args); + } +} + +void WebSocketClass::handleOnOpen() { + if (_onOpenEvent.isFunction()) { + //QScriptValueList args; + //args << ("received: " + message); + _onOpenEvent.call();// QScriptValue(), args); + } +} \ No newline at end of file diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h new file mode 100644 index 0000000000..6f0e3729c0 --- /dev/null +++ b/libraries/script-engine/src/WebSocketClass.h @@ -0,0 +1,118 @@ +// +// WebSocketClass.h +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/4/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// 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_WebSocketClass_h +#define hifi_WebSocketClass_h + +#include +#include +#include + +class WebSocketClass : public QObject { + Q_OBJECT + Q_PROPERTY(QString binaryType READ getBinaryType WRITE setBinaryType) + Q_PROPERTY(ulong bufferedAmount READ getBufferedAmount) + Q_PROPERTY(QString extensions READ getExtensions) + + Q_PROPERTY(QScriptValue onclose READ getOnClose WRITE setOnClose) + Q_PROPERTY(QScriptValue onerror READ getOnError WRITE setOnError) + Q_PROPERTY(QScriptValue onmessage READ getOnMessage WRITE setOnMessage) + Q_PROPERTY(QScriptValue onopen READ getOnOpen WRITE setOnOpen) + + Q_PROPERTY(QString protocol READ getProtocol) + Q_PROPERTY(uint readyState READ getReadyState) + Q_PROPERTY(QString url READ getURL) + + Q_PROPERTY(int CONNECTING READ getConnecting CONSTANT) + Q_PROPERTY(int OPEN READ getOpen CONSTANT) + Q_PROPERTY(int CLOSING READ getClosing CONSTANT) + Q_PROPERTY(int CLOSED READ getClosed CONSTANT) + +public: + WebSocketClass(QScriptEngine* engine, QString url); + ~WebSocketClass(); + + static QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine); + + enum ReadyState { + CONNECTING = 0, + OPEN, + CLOSING, + CLOSED + }; + + int getConnecting() const { return CONNECTING; }; + int getOpen() const { return OPEN; }; + int getClosing() const { return CLOSING; }; + int getClosed() const { return CLOSED; }; + + void setBinaryType(QString binaryType) { _binaryType = binaryType; } + QString getBinaryType() { return _binaryType; } + + // extensions is a empty string until supported in QT + QString getExtensions() { return QString(); } + + // protocol is a empty string until supported in QT + QString getProtocol() { return QString(); } + + //TODO: find buffered amount + ulong getBufferedAmount() { return 0; } + + QString getURL() { return _webSocket.requestUrl().toDisplayString(); } + + uint getReadyState() { + switch (_webSocket.state()) { + case QAbstractSocket::SocketState::HostLookupState: + case QAbstractSocket::SocketState::ConnectingState: + return CONNECTING; + case QAbstractSocket::SocketState::ConnectedState: + case QAbstractSocket::SocketState::BoundState: + case QAbstractSocket::SocketState::ListeningState: + return OPEN; + case QAbstractSocket::SocketState::ClosingState: + return CLOSING; + } + return CLOSED; + } + + void setOnClose(QScriptValue eventFunction) { _onCloseEvent = eventFunction; } + QScriptValue getOnClose() { return _onCloseEvent; } + void setOnError(QScriptValue eventFunction) { _onErrorEvent = eventFunction; } + QScriptValue getOnError() { return _onErrorEvent; } + void setOnMessage(QScriptValue eventFunction) { _onMessageEvent = eventFunction; } + QScriptValue getOnMessage() { return _onMessageEvent; } + void setOnOpen(QScriptValue eventFunction) { _onOpenEvent = eventFunction; } + QScriptValue getOnOpen() { return _onOpenEvent; } + +public slots: + void send(QScriptValue message); + void close(QWebSocketProtocol::CloseCode closeCode, QString reason); + +private: + QWebSocket _webSocket; + QScriptEngine* _engine; + + QScriptValue _onCloseEvent; + QScriptValue _onErrorEvent; + QScriptValue _onMessageEvent; + QScriptValue _onOpenEvent; + + QString _binaryType; + +private slots: + void handleOnClose(); + void handleOnError(QAbstractSocket::SocketError error); + void handleOnMessage(const QString& message); + void handleOnOpen(); + +}; + +#endif // hifi_WebSocketClass_h From 881cfb86dd9364f387ec453967003252df5f3b94 Mon Sep 17 00:00:00 2001 From: samcake Date: Sat, 8 Aug 2015 16:43:55 -0700 Subject: [PATCH 010/170] Updating the render engine dashboard --- examples/utilities/tools/cookies.js | 26 ++++ examples/utilities/tools/renderEngineDebug.js | 118 +++++++----------- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/examples/utilities/tools/cookies.js b/examples/utilities/tools/cookies.js index 751008fd99..b9d634d462 100644 --- a/examples/utilities/tools/cookies.js +++ b/examples/utilities/tools/cookies.js @@ -183,6 +183,24 @@ var CHECK_MARK_COLOR = { this.onValueChanged(resetValue); }; + + Slider.prototype.setMinValue = function(minValue) { + var currentValue = this.getValue(); + this.minValue = minValue; + this.setValue(currentValue); + }; + Slider.prototype.getMinValue = function() { + return this.minValue; + }; + Slider.prototype.setMaxValue = function(maxValue) { + var currentValue = this.getValue(); + this.maxValue = maxValue; + this.setValue(currentValue); + }; + Slider.prototype.getMaxValue = function() { + return this.maxValue; + }; + Slider.prototype.onValueChanged = function(value) {}; Slider.prototype.getHeight = function() { @@ -1396,6 +1414,14 @@ var CHECK_MARK_COLOR = { return null; }; + Panel.prototype.getWidget = function(name) { + var item = this.items[name]; + if (item != null) { + return item.widget; + } + return null; + }; + Panel.prototype.update = function(name) { var item = this.items[name]; if (item != null) { diff --git a/examples/utilities/tools/renderEngineDebug.js b/examples/utilities/tools/renderEngineDebug.js index d50a9c545c..49ac923436 100755 --- a/examples/utilities/tools/renderEngineDebug.js +++ b/examples/utilities/tools/renderEngineDebug.js @@ -12,59 +12,55 @@ Script.include("cookies.js"); var panel = new Panel(10, 100); -panel.newSlider("Num Feed Opaques", 0, 1000, - function(value) { }, - function() { return Scene.getEngineNumFeedOpaqueItems(); }, - function(value) { return (value); } +function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) { + this.subPanel = panel.newSubPanel(name); + + this.subPanel.newSlider("Num Feed", 0, 1, + function(value) { }, + feedGetter, + function(value) { return (value); }); + this.subPanel.newSlider("Num Drawn", 0, 1, + function(value) { }, + drawGetter, + function(value) { return (value); }); + this.subPanel.newSlider("Max Drawn", -1, 1, + capSetter, + capGetter, + function(value) { return (value); }); + + this.update = function () { + var numFeed = this.subPanel.get("Num Feed"); + this.subPanel.set("Num Feed", numFeed); + this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn")); + + var numMax = Math.max(numFeed, 1); + this.subPanel.getWidget("Num Feed").setMaxValue(numMax); + this.subPanel.getWidget("Num Drawn").setMaxValue(numMax); + this.subPanel.getWidget("Max Drawn").setMaxValue(numMax); + }; +}; + +var opaquesCounter = new CounterWidget(panel, "Opaques", + function () { return Scene.getEngineNumFeedOpaqueItems(); }, + function () { return Scene.getEngineNumDrawnOpaqueItems(); }, + function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); }, + function () { return Scene.getEngineMaxDrawnOpaqueItems(); } ); -panel.newSlider("Num Drawn Opaques", 0, 1000, - function(value) { }, - function() { return Scene.getEngineNumDrawnOpaqueItems(); }, - function(value) { return (value); } +var transparentsCounter = new CounterWidget(panel, "Transparents", + function () { return Scene.getEngineNumFeedTransparentItems(); }, + function () { return Scene.getEngineNumDrawnTransparentItems(); }, + function(value) { Scene.setEngineMaxDrawnTransparentItems(value); }, + function () { return Scene.getEngineMaxDrawnTransparentItems(); } ); -panel.newSlider("Max Drawn Opaques", -1, 1000, - function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); }, - function() { return Scene.getEngineMaxDrawnOpaqueItems(); }, - function(value) { return (value); } +var overlaysCounter = new CounterWidget(panel, "Overlays", + function () { return Scene.getEngineNumFeedOverlay3DItems(); }, + function () { return Scene.getEngineNumDrawnOverlay3DItems(); }, + function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); }, + function () { return Scene.getEngineMaxDrawnOverlay3DItems(); } ); -panel.newSlider("Num Feed Transparents", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumFeedTransparentItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Num Drawn Transparents", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumDrawnTransparentItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Max Drawn Transparents", -1, 100, - function(value) { Scene.setEngineMaxDrawnTransparentItems(value); }, - function() { return Scene.getEngineMaxDrawnTransparentItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Num Feed Overlay3Ds", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumFeedOverlay3DItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Num Drawn Overlay3Ds", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumDrawnOverlay3DItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Max Drawn Overlay3Ds", -1, 100, - function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); }, - function() { return Scene.getEngineMaxDrawnOverlay3DItems(); }, - function(value) { return (value); } -); panel.newCheckbox("Display status", function(value) { Scene.setEngineDisplayItemStatus(value); }, @@ -75,31 +71,9 @@ panel.newCheckbox("Display status", var tickTackPeriod = 500; function updateCounters() { - var numFeedOpaques = panel.get("Num Feed Opaques"); - var numFeedTransparents = panel.get("Num Feed Transparents"); - var numFeedOverlay3Ds = panel.get("Num Feed Overlay3Ds"); - - panel.set("Num Feed Opaques", numFeedOpaques); - panel.set("Num Drawn Opaques", panel.get("Num Drawn Opaques")); - panel.set("Num Feed Transparents", numFeedTransparents); - panel.set("Num Drawn Transparents", panel.get("Num Drawn Transparents")); - panel.set("Num Feed Overlay3Ds", numFeedOverlay3Ds); - panel.set("Num Drawn Overlay3Ds", panel.get("Num Drawn Overlay3Ds")); - - var numMax = Math.max(numFeedOpaques * 1.2, 1); - panel.getWidget("Num Feed Opaques").setMaxValue(numMax); - panel.getWidget("Num Drawn Opaques").setMaxValue(numMax); - panel.getWidget("Max Drawn Opaques").setMaxValue(numMax); - - numMax = Math.max(numFeedTransparents * 1.2, 1); - panel.getWidget("Num Feed Transparents").setMaxValue(numMax); - panel.getWidget("Num Drawn Transparents").setMaxValue(numMax); - panel.getWidget("Max Drawn Transparents").setMaxValue(numMax); - - numMax = Math.max(numFeedOverlay3Ds * 1.2, 1); - panel.getWidget("Num Feed Overlay3Ds").setMaxValue(numMax); - panel.getWidget("Num Drawn Overlay3Ds").setMaxValue(numMax); - panel.getWidget("Max Drawn Overlay3Ds").setMaxValue(numMax); + opaquesCounter.update(); + transparentsCounter.update(); + overlaysCounter.update(); } Script.setInterval(updateCounters, tickTackPeriod); From 4fe8f49d4ccd548641cf9c7a3fca61b254fe7378 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 11 Aug 2015 23:47:40 -0700 Subject: [PATCH 011/170] move origin to center of domain --- examples/edit.js | 8 ++++++-- interface/src/Util.cpp | 2 ++ libraries/entities/src/AddEntityOperator.cpp | 2 ++ libraries/entities/src/EntityItem.cpp | 13 +++++++++++-- libraries/entities/src/EntityItem.h | 1 + libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/EntitySimulation.cpp | 6 ++++-- libraries/entities/src/EntityTreeElement.cpp | 8 ++++---- libraries/entities/src/MovingEntitiesOperator.cpp | 2 +- libraries/entities/src/UpdateEntityOperator.cpp | 4 ++-- libraries/networking/src/udt/PacketHeaders.h | 1 + libraries/octree/src/OctreeConstants.h | 3 ++- libraries/octree/src/OctreeElement.cpp | 5 +++-- 13 files changed, 40 insertions(+), 17 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index 0f9c9fc4dd..4cc38a0c3d 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1019,9 +1019,13 @@ function getPositionToCreateEntity() { var placementPosition = Vec3.sum(Camera.position, offset); var cameraPosition = Camera.position; + + var HALF_TREE_SCALE = 16384 / 2; - var cameraOutOfBounds = cameraPosition.x < 0 || cameraPosition.y < 0 || cameraPosition.z < 0; - var placementOutOfBounds = placementPosition.x < 0 || placementPosition.y < 0 || placementPosition.z < 0; + var cameraOutOfBounds = cameraPosition.x < -HALF_TREE_SCALE || cameraPosition.y < -HALF_TREE_SCALE || + cameraPosition.z < -HALF_TREE_SCALE; + var placementOutOfBounds = placementPosition.x < -HALF_TREE_SCALE || placementPosition.y < -HALF_TREE_SCALE || + placementPosition.z < -HALF_TREE_SCALE; if (cameraOutOfBounds && placementOutOfBounds) { return null; diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 3a01367fc7..f62a6b8fc5 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -43,6 +43,8 @@ void renderWorldBox(gpu::Batch& batch) { auto transform = Transform{}; batch.setModelTransform(transform); + + // FIXME - new origin tweaks need to be done to this geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(TREE_SCALE, 0.0f, 0.0f), red); geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, TREE_SCALE, 0.0f), green); geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, TREE_SCALE), blue); diff --git a/libraries/entities/src/AddEntityOperator.cpp b/libraries/entities/src/AddEntityOperator.cpp index b85d12c2da..db9a18a3e9 100644 --- a/libraries/entities/src/AddEntityOperator.cpp +++ b/libraries/entities/src/AddEntityOperator.cpp @@ -25,6 +25,8 @@ AddEntityOperator::AddEntityOperator(EntityTree* tree, { // caller must have verified existence of newEntity assert(_newEntity); + + // FIXME - how does this change for new origin??? _newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, (float)TREE_SCALE); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 205d1c339e..97704985aa 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -634,7 +634,11 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // but since we're using macros below we have to temporarily modify overwriteLocalData. bool oldOverwrite = overwriteLocalData; overwriteLocalData = overwriteLocalData && !weOwnSimulation; - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + if (args.bitstreamVersion >= VERSION_ENTITIES_CENTER_ORIGIN) { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + } else { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionOldOrigin); + } READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); @@ -656,7 +660,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } else { // legacy order of packing here // TODO: purge this logic in a few months from now (2015.07) - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionOldOrigin); READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); @@ -1307,6 +1311,11 @@ void EntityItem::updatePosition(const glm::vec3& value) { } } +void EntityItem::updatePositionOldOrigin(const glm::vec3& value) { + glm::vec3 newValue = value - glm::vec3(HALF_TREE_SCALE); + updatePosition(newValue); +} + void EntityItem::updateDimensions(const glm::vec3& value) { auto delta = glm::distance(getDimensions(), value); if (delta > IGNORE_DIMENSIONS_DELTA) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 57f8883cea..12e6c1b3e7 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -361,6 +361,7 @@ public: // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags void updatePosition(const glm::vec3& value); + void updatePositionOldOrigin(const glm::vec3& value); void updateDimensions(const glm::vec3& value); void updateRotation(const glm::quat& rotation); void updateDensity(float value); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 8085c24d90..1e8cf51ef3 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -253,7 +253,7 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object // define these inline here so the macros work inline void EntityItemProperties::setPosition(const glm::vec3& value) - { _position = glm::clamp(value, 0.0f, (float)TREE_SCALE); _positionChanged = true; } + { _position = glm::clamp(value, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); _positionChanged = true; } inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { debug << "EntityItemProperties[" << "\n"; diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index f2bd1e873e..c614b62c91 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -113,7 +113,8 @@ void EntitySimulation::sortEntitiesThatMoved() { // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. PerformanceTimer perfTimer("sortingEntities"); MovingEntitiesOperator moveOperator(_entityTree); - AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); + AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE), + (float)TREE_SCALE); SetOfEntities::iterator itemItr = _entitiesToSort.begin(); while (itemItr != _entitiesToSort.end()) { EntityItemPointer entity = *itemItr; @@ -195,7 +196,8 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & EntityItem::DIRTY_POSITION) { - AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); + AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE), + (float)TREE_SCALE); AACube newCube = entity->getMaximumAACube(); if (!domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 856550f297..a023f46c5e 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -444,14 +444,14 @@ bool EntityTreeElement::bestFitBounds(const AABox& bounds) const { } bool EntityTreeElement::containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const { - glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE); - glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE); + glm::vec3 clampedMin = glm::clamp(minPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); + glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); return _cube.contains(clampedMin) && _cube.contains(clampedMax); } bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const { - glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE); - glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE); + glm::vec3 clampedMin = glm::clamp(minPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); + glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) { diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index ddfea13d07..7dd1ab849c 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -52,7 +52,7 @@ MovingEntitiesOperator::~MovingEntitiesOperator() { void MovingEntitiesOperator::addEntityToMoveList(EntityItemPointer entity, const AACube& newCube) { EntityTreeElement* oldContainingElement = _tree->getContainingElement(entity->getEntityItemID()); - AABox newCubeClamped = newCube.clamp(0.0f, (float)TREE_SCALE); + AABox newCubeClamped = newCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); if (_wantDebug) { qCDebug(entities) << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------"; diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index 6720839da0..991d725f97 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -47,7 +47,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, // which can handle all potential rotations? // the getMaximumAACube is the relaxed form. _oldEntityCube = _existingEntity->getMaximumAACube(); - _oldEntityBox = _oldEntityCube.clamp(0.0f, (float)TREE_SCALE); // clamp to domain bounds + _oldEntityBox = _oldEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds // If the old properties doesn't contain the properties required to calculate a bounding box, // get them from the existing entity. Registration point is required to correctly calculate @@ -123,7 +123,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, } } - _newEntityBox = _newEntityCube.clamp(0.0f, (float)TREE_SCALE); // clamp to domain bounds + _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds if (_wantDebug) { diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 1aeadb1af9..613ea4913d 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -142,5 +142,6 @@ const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE const PacketVersion VERSION_ENTITIES_NEW_PROTOCOL_LAYER = 35; const PacketVersion VERSION_POLYVOX_TEXTURES = 36; const PacketVersion VERSION_ENTITIES_POLYLINE = 37; +const PacketVersion VERSION_ENTITIES_CENTER_ORIGIN = 38; #endif // hifi_PacketHeaders_h \ No newline at end of file diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index 4a1baea2d5..acfa1d14c6 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -17,7 +17,8 @@ const quint64 CHANGE_FUDGE = 1000 * 200; // useconds of fudge in determining if we want to resend changed voxels -const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe +const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe +const int HALF_TREE_SCALE = TREE_SCALE / 2; // This controls the LOD. Larger number will make smaller voxels visible at greater distance. const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f; diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 85ea0caef0..2bbacccf95 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -191,6 +191,7 @@ void OctreeElement::calculateAACube() { // this tells you the "size" of the voxel float voxelScale = (float)TREE_SCALE / powf(2.0f, numberOfThreeBitSectionsInCode(getOctalCode())); corner *= (float)TREE_SCALE; + corner -= (float)HALF_TREE_SCALE; _cube.setBox(corner, voxelScale); } @@ -717,8 +718,8 @@ int OctreeElement::getMyChildContaining(const AACube& cube) const { } // Determine which of our children the minimum and maximum corners of the cube live in... - glm::vec3 cubeCornerMinimum = glm::clamp(cube.getCorner(), 0.0f, (float)TREE_SCALE); - glm::vec3 cubeCornerMaximum = glm::clamp(cube.calcTopFarLeft(), 0.0f, (float)TREE_SCALE); + glm::vec3 cubeCornerMinimum = glm::clamp(cube.getCorner(), (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); + glm::vec3 cubeCornerMaximum = glm::clamp(cube.calcTopFarLeft(), (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); if (_cube.contains(cubeCornerMinimum) && _cube.contains(cubeCornerMaximum)) { int childIndexCubeMinimum = getMyChildContainingPoint(cubeCornerMinimum); From 85603dab29a622d208b4030858ed1e8df36eb4f5 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 11 Aug 2015 23:51:07 -0700 Subject: [PATCH 012/170] bump entity packet version --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 0146abdb7c..d2b116a9d5 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType::Value packetType) { case EntityAdd: case EntityEdit: case EntityData: - return VERSION_ENTITIES_POLYLINE; + return VERSION_ENTITIES_CENTER_ORIGIN; default: return 11; } From 7c77e0e941f3fe9b3ceda0a079a31b8d63ace6fa Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 13 Aug 2015 18:21:50 +0200 Subject: [PATCH 013/170] initial WebSocketServer --- libraries/script-engine/src/ScriptEngine.cpp | 4 ++ .../script-engine/src/WebSocketClass.cpp | 1 - .../src/WebSocketServerClass.cpp | 44 +++++++++++++++++++ .../script-engine/src/WebSocketServerClass.h | 39 ++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 libraries/script-engine/src/WebSocketServerClass.cpp create mode 100644 libraries/script-engine/src/WebSocketServerClass.h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 861034a6dc..bde0643c51 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -39,6 +39,7 @@ #include "TypedArrays.h" #include "XMLHttpRequestClass.h" #include "WebSocketClass.h" +#include "WebSocketServerClass.h" #include "SceneScriptingInterface.h" @@ -347,6 +348,9 @@ void ScriptEngine::init() { QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor); globalObject().setProperty("WebSocket", webSocketConstructorValue); + QScriptValue webSocketServerConstructorValue = newFunction(WebSocketServerClass::constructor); + globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); + QScriptValue printConstructorValue = newFunction(debugPrint); globalObject().setProperty("print", printConstructorValue); diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 1b8d305b16..f116c3a0e4 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -64,7 +64,6 @@ void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) { void WebSocketClass::handleOnMessage(const QString& message) { if (_onMessageEvent.isFunction()) { - QScriptValueList args; QScriptValue arg = _engine->newObject(); arg.setProperty("data", message); diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp new file mode 100644 index 0000000000..86f6506375 --- /dev/null +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -0,0 +1,44 @@ +// +// WebSocketServerClass.cpp +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/10/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// Making WebSocketServer accessible through scripting. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngine.h" +#include "WebSocketServerClass.h" + +WebSocketServerClass::WebSocketServerClass(QScriptEngine* engine, const QString& serverName, quint16 port) : + _engine(engine), + _webSocketServer(serverName, QWebSocketServer::SslMode::NonSecureMode) +{ + _webSocketServer.listen(QHostAddress::Any, port); +} + +QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine) { + // the serverName is used in handshakes + QString serverName = QStringLiteral("HighFidelity - Scripted WebSocket Listener"); + // port 0 will auto-assign a free port + quint16 port = 0; + QScriptValue callee = context->callee(); + if (context->argumentCount() > 0) { + QScriptValue options = context->argument(0); + QScriptValue portOption = options.property(QStringLiteral("port")); + if (portOption.isValid() && portOption.isNumber()) { + port = portOption.toNumber(); + } + } + return engine->newQObject(new WebSocketServerClass(engine, serverName, port)); +} + +WebSocketServerClass::~WebSocketServerClass() { + if (_webSocketServer.isListening()) { + _webSocketServer.close(); + } +} diff --git a/libraries/script-engine/src/WebSocketServerClass.h b/libraries/script-engine/src/WebSocketServerClass.h new file mode 100644 index 0000000000..18fcdc693e --- /dev/null +++ b/libraries/script-engine/src/WebSocketServerClass.h @@ -0,0 +1,39 @@ +// +// WebSocketServerClass.h +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/10/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// 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_WebSocketServerClass_h +#define hifi_WebSocketServerClass_h + +#include +#include +#include + +class WebSocketServerClass : public QObject { + Q_OBJECT + +public: + WebSocketServerClass(QScriptEngine* engine, const QString& serverName, quint16 port); + ~WebSocketServerClass(); + + static QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine); + +private: + QWebSocketServer _webSocketServer; + QScriptEngine* _engine; + +signals: + void newConnection(); + + + +}; + +#endif // hifi_WebSocketServerClass_h From ea834609c2cb6c28198b2417e916f58a37529274 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 10:33:31 -0700 Subject: [PATCH 014/170] Kill the AvatarAppearanceDialog and rewire necessary parts of PreferencesDialog. --- interface/src/ui/AvatarAppearanceDialog.cpp | 215 --------- interface/src/ui/AvatarAppearanceDialog.h | 64 --- interface/src/ui/DialogsManager.cpp | 10 - interface/src/ui/DialogsManager.h | 3 - interface/src/ui/PreferencesDialog.cpp | 38 +- interface/src/ui/PreferencesDialog.h | 5 +- interface/ui/avatarAppearance.ui | 471 -------------------- interface/ui/preferencesDialog.ui | 5 - 8 files changed, 32 insertions(+), 779 deletions(-) delete mode 100644 interface/src/ui/AvatarAppearanceDialog.cpp delete mode 100644 interface/src/ui/AvatarAppearanceDialog.h delete mode 100644 interface/ui/avatarAppearance.ui diff --git a/interface/src/ui/AvatarAppearanceDialog.cpp b/interface/src/ui/AvatarAppearanceDialog.cpp deleted file mode 100644 index 54e48dca26..0000000000 --- a/interface/src/ui/AvatarAppearanceDialog.cpp +++ /dev/null @@ -1,215 +0,0 @@ -// -// AvatarAppearanceDialog.cpp -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/20/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include - -#include -#include -#include -#include - -#include "Application.h" -#include "MainWindow.h" -#include "LODManager.h" -#include "Menu.h" -#include "AvatarAppearanceDialog.h" -#include "Snapshot.h" -#include "UserActivityLogger.h" -#include "UIUtil.h" -#include "ui/DialogsManager.h" -#include "ui/PreferencesDialog.h" - -AvatarAppearanceDialog::AvatarAppearanceDialog(QWidget* parent) : - QDialog(parent) { - - setAttribute(Qt::WA_DeleteOnClose); - - ui.setupUi(this); - - loadAvatarAppearance(); - - connect(ui.defaultButton, &QPushButton::clicked, this, &AvatarAppearanceDialog::accept); - - connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &AvatarAppearanceDialog::openHeadModelBrowser); - connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &AvatarAppearanceDialog::openBodyModelBrowser); - connect(ui.buttonBrowseFullAvatar, &QPushButton::clicked, this, &AvatarAppearanceDialog::openFullAvatarModelBrowser); - - connect(ui.useSeparateBodyAndHead, &QRadioButton::clicked, this, &AvatarAppearanceDialog::useSeparateBodyAndHead); - connect(ui.useFullAvatar, &QRadioButton::clicked, this, &AvatarAppearanceDialog::useFullAvatar); - - connect(Application::getInstance(), &Application::headURLChanged, this, &AvatarAppearanceDialog::headURLChanged); - connect(Application::getInstance(), &Application::bodyURLChanged, this, &AvatarAppearanceDialog::bodyURLChanged); - connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &AvatarAppearanceDialog::fullAvatarURLChanged); - - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - ui.bodyNameLabel->setText("Body - " + myAvatar->getBodyModelName()); - ui.headNameLabel->setText("Head - " + myAvatar->getHeadModelName()); - ui.fullAvatarNameLabel->setText("Full Avatar - " + myAvatar->getFullAvartarModelName()); - - UIUtil::scaleWidgetFontSizes(this); -} - -void AvatarAppearanceDialog::useSeparateBodyAndHead(bool checked) { - QUrl headURL(ui.faceURLEdit->text()); - QUrl bodyURL(ui.skeletonURLEdit->text()); - DependencyManager::get()->getMyAvatar()->useHeadAndBodyURLs(headURL, bodyURL); - setUseFullAvatar(!checked); -} - -void AvatarAppearanceDialog::useFullAvatar(bool checked) { - QUrl fullAvatarURL(ui.fullAvatarURLEdit->text()); - DependencyManager::get()->getMyAvatar()->useFullAvatarURL(fullAvatarURL); - setUseFullAvatar(checked); -} - -void AvatarAppearanceDialog::setUseFullAvatar(bool useFullAvatar) { - _useFullAvatar = useFullAvatar; - ui.faceURLEdit->setEnabled(!_useFullAvatar); - ui.skeletonURLEdit->setEnabled(!_useFullAvatar); - ui.fullAvatarURLEdit->setEnabled(_useFullAvatar); - - ui.useFullAvatar->setChecked(_useFullAvatar); - ui.useSeparateBodyAndHead->setChecked(!_useFullAvatar); - - QPointer prefs = DependencyManager::get()->getPreferencesDialog(); - if (prefs) { // Preferences dialog may have been closed - prefs->avatarDescriptionChanged(); - } -} - -void AvatarAppearanceDialog::headURLChanged(const QString& newValue, const QString& modelName) { - ui.faceURLEdit->setText(newValue); - setUseFullAvatar(false); - ui.headNameLabel->setText("Head - " + modelName); -} - -void AvatarAppearanceDialog::bodyURLChanged(const QString& newValue, const QString& modelName) { - ui.skeletonURLEdit->setText(newValue); - setUseFullAvatar(false); - ui.bodyNameLabel->setText("Body - " + modelName); -} - -void AvatarAppearanceDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) { - ui.fullAvatarURLEdit->setText(newValue); - setUseFullAvatar(true); - ui.fullAvatarNameLabel->setText("Full Avatar - " + modelName); -} - -void AvatarAppearanceDialog::accept() { - saveAvatarAppearance(); - - QPointer prefs = DependencyManager::get()->getPreferencesDialog(); - if (prefs) { // Preferences dialog may have been closed - prefs->avatarDescriptionChanged(); - } - - close(); - delete _marketplaceWindow; - _marketplaceWindow = NULL; -} - -void AvatarAppearanceDialog::setHeadUrl(QString modelUrl) { - ui.faceURLEdit->setText(modelUrl); -} - -void AvatarAppearanceDialog::setSkeletonUrl(QString modelUrl) { - ui.skeletonURLEdit->setText(modelUrl); -} - -void AvatarAppearanceDialog::openFullAvatarModelBrowser() { - auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; - auto WIDTH = 900; - auto HEIGHT = 700; - if (!_marketplaceWindow) { - _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); - } - _marketplaceWindow->setVisible(true); -} - -void AvatarAppearanceDialog::openHeadModelBrowser() { - auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; - auto WIDTH = 900; - auto HEIGHT = 700; - if (!_marketplaceWindow) { - _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); - } - _marketplaceWindow->setVisible(true); -} - -void AvatarAppearanceDialog::openBodyModelBrowser() { - auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; - auto WIDTH = 900; - auto HEIGHT = 700; - if (!_marketplaceWindow) { - _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); - } - _marketplaceWindow->setVisible(true); -} - -void AvatarAppearanceDialog::resizeEvent(QResizeEvent *resizeEvent) { - - // keep buttons panel at the bottom - ui.buttonsPanel->setGeometry(0, - size().height() - ui.buttonsPanel->height(), - size().width(), - ui.buttonsPanel->height()); - - // set width and height of srcollarea to match bottom panel and width - ui.scrollArea->setGeometry(ui.scrollArea->geometry().x(), ui.scrollArea->geometry().y(), - size().width(), - size().height() - ui.buttonsPanel->height() - ui.scrollArea->geometry().y()); - -} - -void AvatarAppearanceDialog::loadAvatarAppearance() { - - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - - _useFullAvatar = myAvatar->getUseFullAvatar(); - _fullAvatarURLString = myAvatar->getFullAvatarURLFromPreferences().toString(); - _headURLString = myAvatar->getHeadURLFromPreferences().toString(); - _bodyURLString = myAvatar->getBodyURLFromPreferences().toString(); - - ui.fullAvatarURLEdit->setText(_fullAvatarURLString); - ui.faceURLEdit->setText(_headURLString); - ui.skeletonURLEdit->setText(_bodyURLString); - setUseFullAvatar(_useFullAvatar); -} - -void AvatarAppearanceDialog::saveAvatarAppearance() { - - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - - QUrl headURL(ui.faceURLEdit->text()); - QString headURLString = headURL.toString(); - - QUrl bodyURL(ui.skeletonURLEdit->text()); - QString bodyURLString = bodyURL.toString(); - - QUrl fullAvatarURL(ui.fullAvatarURLEdit->text()); - QString fullAvatarURLString = fullAvatarURL.toString(); - - bool somethingChanged = - _useFullAvatar != myAvatar->getUseFullAvatar() || - fullAvatarURLString != myAvatar->getFullAvatarURLFromPreferences().toString() || - headURLString != myAvatar->getHeadURLFromPreferences().toString() || - bodyURLString != myAvatar->getBodyURLFromPreferences().toString(); - - if (somethingChanged) { - if (_useFullAvatar) { - myAvatar->useFullAvatarURL(fullAvatarURL); - } else { - myAvatar->useHeadAndBodyURLs(headURL, bodyURL); - } - } -} diff --git a/interface/src/ui/AvatarAppearanceDialog.h b/interface/src/ui/AvatarAppearanceDialog.h deleted file mode 100644 index be30caeedb..0000000000 --- a/interface/src/ui/AvatarAppearanceDialog.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// AvatarAppearanceDialog.h -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/20/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_AvatarAppearanceDialog_h -#define hifi_AvatarAppearanceDialog_h - -#include "ui_avatarAppearance.h" - -#include -#include - -#include "scripting/WebWindowClass.h" - -class AvatarAppearanceDialog : public QDialog { - Q_OBJECT - -public: - AvatarAppearanceDialog(QWidget* parent = nullptr); - -protected: - void resizeEvent(QResizeEvent* resizeEvent); - -private: - void loadAvatarAppearance(); - void saveAvatarAppearance(); - void openHeadModelBrowser(); - void openBodyModelBrowser(); - void openFullAvatarModelBrowser(); - void setUseFullAvatar(bool useFullAvatar); - - Ui_AvatarAppearanceDialog ui; - - bool _useFullAvatar; - QString _headURLString; - QString _bodyURLString; - QString _fullAvatarURLString; - - - QString _displayNameString; - - WebWindowClass* _marketplaceWindow = NULL; - -private slots: - void accept(); - void setHeadUrl(QString modelUrl); - void setSkeletonUrl(QString modelUrl); - void headURLChanged(const QString& newValue, const QString& modelName); - void bodyURLChanged(const QString& newValue, const QString& modelName); - void fullAvatarURLChanged(const QString& newValue, const QString& modelName); - void useSeparateBodyAndHead(bool checked); - void useFullAvatar(bool checked); - - -}; - -#endif // hifi_AvatarAppearanceDialog_h diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index ac5e6833fb..d8671f9798 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -20,7 +20,6 @@ #include "AddressBarDialog.h" #include "AnimationsDialog.h" #include "AttachmentsDialog.h" -#include "AvatarAppearanceDialog.h" #include "BandwidthDialog.h" #include "CachesSizeDialog.h" #include "DiskCacheEditor.h" @@ -88,15 +87,6 @@ void DialogsManager::editPreferences() { } } -void DialogsManager::changeAvatarAppearance() { - if (!_avatarAppearanceDialog) { - maybeCreateDialog(_avatarAppearanceDialog); - _avatarAppearanceDialog->show(); - } else { - _avatarAppearanceDialog->close(); - } -} - void DialogsManager::editAttachments() { if (!_attachmentsDialog) { maybeCreateDialog(_attachmentsDialog); diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 09e0274d86..2db700e72a 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -34,7 +34,6 @@ class OctreeStatsDialog; class PreferencesDialog; class ScriptEditorWindow; class QMessageBox; -class AvatarAppearanceDialog; class DomainConnectionDialog; class UpdateDialog; @@ -66,7 +65,6 @@ public slots: void hmdTools(bool showTools); void showScriptEditor(); void showIRCLink(); - void changeAvatarAppearance(); void showDomainConnectionDialog(); // Application Update @@ -110,7 +108,6 @@ private: QPointer _octreeStatsDialog; QPointer _preferencesDialog; QPointer _scriptEditor; - QPointer _avatarAppearanceDialog; QPointer _domainConnectionDialog; QPointer _updateDialog; }; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 8e9c164563..cec379c596 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -47,23 +47,25 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser); connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, Application::getInstance(), &Application::loadDefaultScripts); - DialogsManager* dialogsManager = DependencyManager::get().data(); - connect(ui.buttonChangeApperance, &QPushButton::clicked, dialogsManager, &DialogsManager::changeAvatarAppearance); + //FIXME remove DialogsManager* dialogsManager = DependencyManager::get().data(); + //FIXME remove connect(ui.buttonChangeApperance, &QPushButton::clicked, dialogsManager, &DialogsManager::changeAvatarAppearance); + connect(ui.buttonChangeApperance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); + connect(ui.apperanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) { + this->fullAvatarURLChanged(url, ""); + }); - connect(Application::getInstance(), &Application::headURLChanged, this, &PreferencesDialog::headURLChanged); - connect(Application::getInstance(), &Application::bodyURLChanged, this, &PreferencesDialog::bodyURLChanged); + //FIXME remove connect(Application::getInstance(), &Application::headURLChanged, this, &PreferencesDialog::headURLChanged); + //FIXME remove connect(Application::getInstance(), &Application::bodyURLChanged, this, &PreferencesDialog::bodyURLChanged); connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); // move dialog to left side move(parentWidget()->geometry().topLeft()); setFixedHeight(parentWidget()->size().height() - PREFERENCES_HEIGHT_PADDING); - ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); - UIUtil::scaleWidgetFontSizes(this); } -void PreferencesDialog::avatarDescriptionChanged() { +/*FIXME remove void PreferencesDialog::avatarDescriptionChanged() { ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); } @@ -74,9 +76,13 @@ void PreferencesDialog::headURLChanged(const QString& newValue, const QString& m void PreferencesDialog::bodyURLChanged(const QString& newValue, const QString& modelName) { ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); } - +*/ void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) { - ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); + //FIXME remove ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); + ui.apperanceDescription->setText(newValue); + const QString APPEARANCE_LABEL_TEXT("Appearance: "); + ui.appearanceLabel->setText(APPEARANCE_LABEL_TEXT + modelName); + DependencyManager::get()->getMyAvatar()->useFullAvatarURL(newValue, modelName); } void PreferencesDialog::accept() { @@ -103,6 +109,16 @@ void PreferencesDialog::openScriptsLocationBrowser() { ui.scriptsLocationEdit->setText(dir); } } +void PreferencesDialog::openFullAvatarModelBrowser() { + const auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; + const auto WIDTH = 900; + const auto HEIGHT = 700; + if (!_marketplaceWindow) { + _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); + } + _marketplaceWindow->setVisible(true); + +} void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) { @@ -129,6 +145,9 @@ void PreferencesDialog::loadPreferences() { ui.collisionSoundURLEdit->setText(myAvatar->getCollisionSoundURL()); + fullAvatarURLChanged(myAvatar->getFullAvatarURLFromPreferences().toString(), + myAvatar->getFullAvartarModelName()); + ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger)); ui.snapshotLocationEdit->setText(Snapshot::snapshotsLocation.get()); @@ -210,6 +229,7 @@ void PreferencesDialog::savePreferences() { } myAvatar->setCollisionSoundURL(ui.collisionSoundURLEdit->text()); + // avatar model url is already persisted by fullAvatarURLChanged() if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger) != ui.sendDataCheckBox->isChecked()) { diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index 8e699c80a2..6cdb4deba1 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -42,10 +42,11 @@ private: private slots: void accept(); + void openFullAvatarModelBrowser(); void openSnapshotLocationBrowser(); void openScriptsLocationBrowser(); - void headURLChanged(const QString& newValue, const QString& modelName); - void bodyURLChanged(const QString& newValue, const QString& modelName); + /* FIXME remove void headURLChanged(const QString& newValue, const QString& modelName); + void bodyURLChanged(const QString& newValue, const QString& modelName);*/ void fullAvatarURLChanged(const QString& newValue, const QString& modelName); }; diff --git a/interface/ui/avatarAppearance.ui b/interface/ui/avatarAppearance.ui deleted file mode 100644 index 5ebf2c705b..0000000000 --- a/interface/ui/avatarAppearance.ui +++ /dev/null @@ -1,471 +0,0 @@ - - - AvatarAppearanceDialog - - - - 0 - 0 - 500 - 350 - - - - - 0 - 0 - - - - - 500 - 350 - - - - - 500 - 16777215 - - - - - 13 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - true - - - - - 0 - -107 - 485 - 1550 - - - - - 0 - - - 30 - - - 0 - - - 30 - - - 10 - - - - - - - 0 - - - 7 - - - 7 - - - - - - - - - Arial - - - - Use single avatar with Body and Head - - - - - - - - - - 0 - - - 10 - - - 0 - - - 0 - - - - - - - - Arial - - - - Full Avatar - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - fullAvatarURLEdit - - - - - - - - - - - - 0 - 0 - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 0 - - - - - - - - - - Arial - - - - Browse - - - - 0 - 0 - - - - - - - - - - - - - - - - - - - - - - - - Arial - - - - Use separate Body and Head avatar files - - - - - - - - - 0 - - - 10 - - - 7 - - - 7 - - - - - - - - - Arial - - - - Head - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - - - - 0 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - Arial - - - - Browse - - - - 0 - 0 - - - - - - - - - - - - - - - 0 - - - 10 - - - 7 - - - 7 - - - - - - - Arial - - - - Body - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - skeletonURLEdit - - - - - - - - 0 - - - - - - 0 - 0 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - Arial - - - - Browse - - - - 0 - 0 - - - - - - - - - - - - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - Arial - - - - Close - - - true - - - false - - - - - - - - - - - - diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index df6d28c07b..71103da654 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -294,11 +294,6 @@ Arial - - - false - - From 62941c09140feeffcbe3e7ceb72efac1f024305f Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 12:39:30 -0700 Subject: [PATCH 015/170] Remove dead code. --- interface/src/Application.cpp | 27 +---- interface/src/avatar/Avatar.cpp | 13 +-- interface/src/avatar/Avatar.h | 2 - interface/src/avatar/MyAvatar.cpp | 142 +------------------------ interface/src/avatar/MyAvatar.h | 24 +---- interface/src/ui/PreferencesDialog.cpp | 18 ---- interface/src/ui/PreferencesDialog.h | 2 - libraries/avatars/src/AvatarData.cpp | 2 +- libraries/avatars/src/AvatarData.h | 7 +- 9 files changed, 12 insertions(+), 225 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dba2ed3234..5dac571045 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3896,23 +3896,12 @@ bool Application::askToSetAvatarUrl(const QString& url) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Question); msgBox.setWindowTitle("Set Avatar"); - QPushButton* headButton = NULL; - QPushButton* bodyButton = NULL; QPushButton* bodyAndHeadButton = NULL; QString modelName = fstMapping["name"].toString(); QString message; QString typeInfo; switch (modelType) { - case FSTReader::HEAD_MODEL: - message = QString("Would you like to use '") + modelName + QString("' for your avatar head?"); - headButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); - break; - - case FSTReader::BODY_ONLY_MODEL: - message = QString("Would you like to use '") + modelName + QString("' for your avatar body?"); - bodyButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); - break; case FSTReader::HEAD_AND_BODY_MODEL: message = QString("Would you like to use '") + modelName + QString("' for your avatar?"); @@ -3920,10 +3909,7 @@ bool Application::askToSetAvatarUrl(const QString& url) { break; default: - message = QString("Would you like to use '") + modelName + QString("' for some part of your avatar head?"); - headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole); - bodyButton = msgBox.addButton(tr("Use for Body"), QMessageBox::ActionRole); - bodyAndHeadButton = msgBox.addButton(tr("Use for Body and Head"), QMessageBox::ActionRole); + message = QString(modelName + QString("Does not support a head and body as required.")); break; } @@ -3932,14 +3918,7 @@ bool Application::askToSetAvatarUrl(const QString& url) { msgBox.exec(); - if (msgBox.clickedButton() == headButton) { - _myAvatar->useHeadURL(url, modelName); - emit headURLChanged(url, modelName); - } else if (msgBox.clickedButton() == bodyButton) { - _myAvatar->useBodyURL(url, modelName); - emit bodyURLChanged(url, modelName); - } else if (msgBox.clickedButton() == bodyAndHeadButton) { - _myAvatar->useFullAvatarURL(url, modelName); + if (msgBox.clickedButton() == bodyAndHeadButton) { emit fullAvatarURLChanged(url, modelName); } else { qCDebug(interfaceapp) << "Declined to use the avatar: " << url; @@ -4426,7 +4405,7 @@ void Application::checkSkeleton() { msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); - _myAvatar->useBodyURL(DEFAULT_BODY_MODEL_URL, "Default"); + _myAvatar->useFullAvatarURL(DEFAULT_FULL_AVATAR_MODEL_URL, DEFAULT_FULL_AVATAR_MODEL_NAME); } else { _physicsEngine.setCharacterController(_myAvatar->getCharacterController()); } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 8ba58217c3..b537d5a5d8 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -953,20 +952,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { void Avatar::setFaceModelURL(const QUrl& faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); - const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_head.fst"); - getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL, true, !isMyAvatar()); + getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FULL_AVATAR_MODEL_URL, true, !isMyAvatar()); } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); - const QUrl DEFAULT_FULL_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst"); - const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst"); - if (isMyAvatar()) { - _skeletonModel.setURL(_skeletonModelURL, - getUseFullAvatar() ? DEFAULT_FULL_MODEL_URL : DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); - } else { - _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); - } + _skeletonModel.setURL(_skeletonModelURL, DEFAULT_FULL_AVATAR_MODEL_URL, true, !isMyAvatar()); } void Avatar::setAttachmentData(const QVector& attachmentData) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index a51da387d0..2adff1ce02 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -149,8 +149,6 @@ public: Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; } Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; } Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } - - virtual bool getUseFullAvatar() const { return false; } /// Scales a world space position vector relative to the avatar position and scale /// \param vector position to be scaled. Will store the result diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2cc1105fdc..05bfac1523 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -651,12 +651,7 @@ void MyAvatar::saveData() { settings.setValue("leanScale", _leanScale); settings.setValue("scale", _targetScale); - settings.setValue("useFullAvatar", _useFullAvatar); settings.setValue("fullAvatarURL", _fullAvatarURLFromPreferences); - settings.setValue("faceModelURL", _headURLFromPreferences); - settings.setValue("skeletonModelURL", _skeletonURLFromPreferences); - settings.setValue("headModelName", _headModelName); - settings.setValue("bodyModelName", _bodyModelName); settings.setValue("fullAvatarModelName", _fullAvatarModelName); settings.beginWriteArray("attachmentData"); @@ -726,61 +721,10 @@ void MyAvatar::loadData() { _targetScale = loadSetting(settings, "scale", 1.0f); setScale(_scale); - // The old preferences only stored the face and skeleton URLs, we didn't track if the user wanted to use 1 or 2 urls - // for their avatar, So we need to attempt to detect this old case and set our new preferences accordingly. If - // the head URL is empty, then we will assume they are using a full url... - bool isOldSettings = !(settings.contains("useFullAvatar") || settings.contains("fullAvatarURL")); - - _useFullAvatar = settings.value("useFullAvatar").toBool(); - _headURLFromPreferences = settings.value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl(); _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", DEFAULT_FULL_AVATAR_MODEL_URL).toUrl(); - _skeletonURLFromPreferences = settings.value("skeletonModelURL", DEFAULT_BODY_MODEL_URL).toUrl(); - _headModelName = settings.value("headModelName", DEFAULT_HEAD_MODEL_NAME).toString(); - _bodyModelName = settings.value("bodyModelName", DEFAULT_BODY_MODEL_NAME).toString(); _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); - if (isOldSettings) { - bool assumeFullAvatar = _headURLFromPreferences.isEmpty(); - _useFullAvatar = assumeFullAvatar; - - if (_useFullAvatar) { - _fullAvatarURLFromPreferences = settings.value("skeletonModelURL").toUrl(); - _headURLFromPreferences = DEFAULT_HEAD_MODEL_URL; - _skeletonURLFromPreferences = DEFAULT_BODY_MODEL_URL; - - QVariantHash fullAvatarFST = FSTReader::downloadMapping(_fullAvatarURLFromPreferences.toString()); - - _headModelName = "Default"; - _bodyModelName = "Default"; - _fullAvatarModelName = fullAvatarFST["name"].toString(); - - } else { - _fullAvatarURLFromPreferences = DEFAULT_FULL_AVATAR_MODEL_URL; - _skeletonURLFromPreferences = settings.value("skeletonModelURL", DEFAULT_BODY_MODEL_URL).toUrl(); - - if (_skeletonURLFromPreferences == DEFAULT_BODY_MODEL_URL) { - _bodyModelName = DEFAULT_BODY_MODEL_NAME; - } else { - QVariantHash bodyFST = FSTReader::downloadMapping(_skeletonURLFromPreferences.toString()); - _bodyModelName = bodyFST["name"].toString(); - } - - if (_headURLFromPreferences == DEFAULT_HEAD_MODEL_URL) { - _headModelName = DEFAULT_HEAD_MODEL_NAME; - } else { - QVariantHash headFST = FSTReader::downloadMapping(_headURLFromPreferences.toString()); - _headModelName = headFST["name"].toString(); - } - - _fullAvatarModelName = "Default"; - } - } - - if (_useFullAvatar) { - useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - } else { - useHeadAndBodyURLs(_headURLFromPreferences, _skeletonURLFromPreferences, _headModelName, _bodyModelName); - } + useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); QVector attachmentData; int attachmentCount = settings.beginReadArray("attachmentData"); @@ -1015,29 +959,6 @@ void MyAvatar::clearJointAnimationPriorities() { } } -QString MyAvatar::getModelDescription() const { - QString result; - if (_useFullAvatar) { - if (!getFullAvartarModelName().isEmpty()) { - result = "Full Avatar \"" + getFullAvartarModelName() + "\""; - } else { - result = "Full Avatar \"" + _fullAvatarURLFromPreferences.fileName() + "\""; - } - } else { - if (!getHeadModelName().isEmpty()) { - result = "Head \"" + getHeadModelName() + "\""; - } else { - result = "Head \"" + _headURLFromPreferences.fileName() + "\""; - } - if (!getBodyModelName().isEmpty()) { - result += " and Body \"" + getBodyModelName() + "\""; - } else { - result += " and Body \"" + _skeletonURLFromPreferences.fileName() + "\""; - } - } - return result; -} - void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) { Avatar::setFaceModelURL(faceModelURL); @@ -1064,8 +985,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN return; } - _useFullAvatar = true; - if (_fullAvatarURLFromPreferences != fullAvatarURL) { _fullAvatarURLFromPreferences = fullAvatarURL; if (modelName.isEmpty()) { @@ -1087,59 +1006,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN sendIdentityPacket(); } -void MyAvatar::useHeadURL(const QUrl& headURL, const QString& modelName) { - useHeadAndBodyURLs(headURL, _skeletonURLFromPreferences, modelName, _bodyModelName); -} - -void MyAvatar::useBodyURL(const QUrl& bodyURL, const QString& modelName) { - useHeadAndBodyURLs(_headURLFromPreferences, bodyURL, _headModelName, modelName); -} - -void MyAvatar::useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, const QString& headName, const QString& bodyName) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "useFullAvatarURL", Qt::BlockingQueuedConnection, - Q_ARG(const QUrl&, headURL), - Q_ARG(const QUrl&, bodyURL), - Q_ARG(const QString&, headName), - Q_ARG(const QString&, bodyName)); - return; - } - - _useFullAvatar = false; - - if (_headURLFromPreferences != headURL) { - _headURLFromPreferences = headURL; - if (headName.isEmpty()) { - QVariantHash headFST = FSTReader::downloadMapping(_headURLFromPreferences.toString()); - _headModelName = headFST["name"].toString(); - } else { - _headModelName = headName; - } - } - - if (_skeletonURLFromPreferences != bodyURL) { - _skeletonURLFromPreferences = bodyURL; - if (bodyName.isEmpty()) { - QVariantHash bodyFST = FSTReader::downloadMapping(_skeletonURLFromPreferences.toString()); - _bodyModelName = bodyFST["name"].toString(); - } else { - _bodyModelName = bodyName; - } - } - - if (headURL != getFaceModelURL()) { - setFaceModelURL(headURL); - UserActivityLogger::getInstance().changedModel("head", headURL.toString()); - } - - if (bodyURL != getSkeletonModelURL()) { - setSkeletonModelURL(bodyURL); - UserActivityLogger::getInstance().changedModel("skeleton", bodyURL.toString()); - } - sendIdentityPacket(); -} - - void MyAvatar::setAttachmentData(const QVector& attachmentData) { Avatar::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { @@ -1295,11 +1161,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { } if (shouldDrawHead != _prevShouldDrawHead) { - if (_useFullAvatar) { - _skeletonModel.setCauterizeBones(!shouldDrawHead); - } else { - getHead()->getFaceModel().setVisibleInScene(shouldDrawHead, scene); - } + _skeletonModel.setCauterizeBones(!shouldDrawHead); } _prevShouldDrawHead = shouldDrawHead; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 88649429f5..0cd56ec99c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -126,22 +126,9 @@ public: virtual void clearJointsData(); Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); - Q_INVOKABLE void useHeadURL(const QUrl& headURL, const QString& modelName = QString()); - Q_INVOKABLE void useBodyURL(const QUrl& bodyURL, const QString& modelName = QString()); - Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, - const QString& headName = QString(), const QString& bodyName = QString()); - - Q_INVOKABLE virtual bool getUseFullAvatar() const { return _useFullAvatar; } Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } - Q_INVOKABLE const QUrl& getHeadURLFromPreferences() const { return _headURLFromPreferences; } - Q_INVOKABLE const QUrl& getBodyURLFromPreferences() const { return _skeletonURLFromPreferences; } - - Q_INVOKABLE const QString& getHeadModelName() const { return _headModelName; } - Q_INVOKABLE const QString& getBodyModelName() const { return _bodyModelName; } Q_INVOKABLE const QString& getFullAvartarModelName() const { return _fullAvatarModelName; } - Q_INVOKABLE QString getModelDescription() const; - virtual void setAttachmentData(const QVector& attachmentData); DynamicCharacterController* getCharacterController() { return &_characterController; } @@ -298,18 +285,9 @@ private: void initHeadBones(); // Avatar Preferences - bool _useFullAvatar = false; QUrl _fullAvatarURLFromPreferences; - QUrl _headURLFromPreferences; - QUrl _skeletonURLFromPreferences; - - QString _headModelName; - QString _bodyModelName; QString _fullAvatarModelName; - RigPointer _rig; - bool _prevShouldDrawHead; - // cache of the current HMD sensor position and orientation // in sensor space. glm::mat4 _hmdSensorMatrix; @@ -330,6 +308,8 @@ private: glm::quat _goToOrientation; std::unordered_set _headBoneSet; + RigPointer _rig; + bool _prevShouldDrawHead; }; #endif // hifi_MyAvatar_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index cec379c596..59701e5cf3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -47,15 +47,10 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser); connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, Application::getInstance(), &Application::loadDefaultScripts); - //FIXME remove DialogsManager* dialogsManager = DependencyManager::get().data(); - //FIXME remove connect(ui.buttonChangeApperance, &QPushButton::clicked, dialogsManager, &DialogsManager::changeAvatarAppearance); connect(ui.buttonChangeApperance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); connect(ui.apperanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) { this->fullAvatarURLChanged(url, ""); }); - - //FIXME remove connect(Application::getInstance(), &Application::headURLChanged, this, &PreferencesDialog::headURLChanged); - //FIXME remove connect(Application::getInstance(), &Application::bodyURLChanged, this, &PreferencesDialog::bodyURLChanged); connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); // move dialog to left side @@ -65,20 +60,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : UIUtil::scaleWidgetFontSizes(this); } -/*FIXME remove void PreferencesDialog::avatarDescriptionChanged() { - ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); -} - -void PreferencesDialog::headURLChanged(const QString& newValue, const QString& modelName) { - ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); -} - -void PreferencesDialog::bodyURLChanged(const QString& newValue, const QString& modelName) { - ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); -} -*/ void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) { - //FIXME remove ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); ui.apperanceDescription->setText(newValue); const QString APPEARANCE_LABEL_TEXT("Appearance: "); ui.appearanceLabel->setText(APPEARANCE_LABEL_TEXT + modelName); diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index 6cdb4deba1..47341b8feb 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -45,8 +45,6 @@ private slots: void openFullAvatarModelBrowser(); void openSnapshotLocationBrowser(); void openScriptsLocationBrowser(); - /* FIXME remove void headURLChanged(const QString& newValue, const QString& modelName); - void bodyURLChanged(const QString& newValue, const QString& modelName);*/ void fullAvatarURLChanged(const QString& newValue, const QString& modelName); }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 5b970a95a3..78fa00eed7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -941,7 +941,7 @@ void AvatarData::setFaceModelURL(const QUrl& faceModelURL) { } void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { - _skeletonModelURL = skeletonModelURL.isEmpty() ? DEFAULT_BODY_MODEL_URL : skeletonModelURL; + _skeletonModelURL = skeletonModelURL.isEmpty() ? DEFAULT_FULL_AVATAR_MODEL_URL : skeletonModelURL; qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 69783652fc..16003594c9 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -48,6 +48,7 @@ typedef unsigned long long quint64; #include #include +//FIXME #include #include #include @@ -107,12 +108,8 @@ const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000; -const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/models/heads/defaultAvatar_head.fst"); -const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/models/skeletons/defaultAvatar_body.fst"); +//FIXME const QUrl DEFAULT_FULL_AVATAR_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst"); const QUrl DEFAULT_FULL_AVATAR_MODEL_URL = QUrl("http://public.highfidelity.io/marketplace/contents/029db3d4-da2c-4cb2-9c08-b9612ba576f5/02949063e7c4aed42ad9d1a58461f56d.fst"); - -const QString DEFAULT_HEAD_MODEL_NAME = QString("Robot"); -const QString DEFAULT_BODY_MODEL_NAME = QString("Robot"); const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); From 4dae01a69c7bafbb10639212640340941adc7e3b Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 12:54:16 -0700 Subject: [PATCH 016/170] Provide a consistent avatar default for the empty string. --- interface/src/avatar/MyAvatar.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 05bfac1523..ddc0b0b9d1 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -999,9 +999,10 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN setFaceModelURL(QString()); } - if (fullAvatarURL != getSkeletonModelURL()) { + const QString& urlString = fullAvatarURL.toString(); + if (urlString.isEmpty() || (fullAvatarURL != getSkeletonModelURL())) { setSkeletonModelURL(fullAvatarURL); - UserActivityLogger::getInstance().changedModel("skeleton", fullAvatarURL.toString()); + UserActivityLogger::getInstance().changedModel("skeleton", urlString); } sendIdentityPacket(); } From cda5b29fd3f2c0340f5d891bdd897aad182b4b07 Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Thu, 13 Aug 2015 14:19:13 -0600 Subject: [PATCH 017/170] Another take at versioning details exchange - checkpoint --- assignment-client/CMakeLists.txt | 1 + assignment-client/src/AssignmentClient.cpp | 3 +-- assignment-client/src/AssignmentClientApp.cpp | 5 ++--- .../macros/ApplicationVersion.h.in | 10 ++++----- cmake/macros/IncludeApplicationVersion.cmake | 22 +++++++++++++++++++ domain-server/CMakeLists.txt | 1 + domain-server/src/DomainServer.cpp | 8 ++----- interface/CMakeLists.txt | 12 ++-------- interface/src/Application.cpp | 2 +- libraries/networking/CMakeLists.txt | 3 ++- libraries/networking/src/Assignment.cpp | 7 ++++++ libraries/networking/src/Assignment.h | 1 + libraries/networking/src/NodeList.cpp | 10 ++++++--- 13 files changed, 53 insertions(+), 32 deletions(-) rename interface/InterfaceVersion.h.in => cmake/macros/ApplicationVersion.h.in (54%) create mode 100644 cmake/macros/IncludeApplicationVersion.cmake diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 2561a1502d..edd68e12bf 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -17,4 +17,5 @@ if (UNIX) target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS}) endif (UNIX) +include_application_version() copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 40aef1c707..ec0e5b9e5a 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -129,6 +129,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri packetReceiver.registerListener(PacketType::CreateAssignment, this, "handleCreateAssignmentPacket"); packetReceiver.registerListener(PacketType::StopNode, this, "handleStopNodePacket"); } + void AssignmentClient::stopAssignmentClient() { qDebug() << "Forced stop of assignment-client."; @@ -172,7 +173,6 @@ void AssignmentClient::aboutToQuit() { qInstallMessageHandler(0); } - void AssignmentClient::setUpStatusToMonitor() { // send a stats packet every 1 seconds connect(&_statsTimerACM, &QTimer::timeout, this, &AssignmentClient::sendStatusPacketToACM); @@ -217,7 +217,6 @@ void AssignmentClient::sendAssignmentRequest() { qDebug() << "Failed to read local assignment server port from shared memory" << "- will send assignment request to previous assignment server socket."; } - } nodeList->sendAssignment(_requestAssignment); diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index 7268f9281b..2edae340c3 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,7 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("assignment-client"); + setApplicationName(BUILD_VERSION); // use the verbose message handler in Logging qInstallMessageHandler(LogHandler::verboseMessageHandler); @@ -93,10 +95,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : Q_UNREACHABLE(); } - const QVariantMap argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); - unsigned int numForks = 0; if (parser.isSet(numChildsOption)) { numForks = parser.value(numChildsOption).toInt(); @@ -139,7 +139,6 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : assignmentPool = parser.value(poolOption); } - QUuid walletUUID; if (argumentVariantMap.contains(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) { walletUUID = argumentVariantMap.value(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION).toString(); diff --git a/interface/InterfaceVersion.h.in b/cmake/macros/ApplicationVersion.h.in similarity index 54% rename from interface/InterfaceVersion.h.in rename to cmake/macros/ApplicationVersion.h.in index 2f902de41c..736d00726c 100644 --- a/interface/InterfaceVersion.h.in +++ b/cmake/macros/ApplicationVersion.h.in @@ -1,11 +1,9 @@ // -// InterfaceVersion.h -// interface/src +// ApplicationVersion.h.in +// cmake/macros // -// Created by Leonardo Murillo on 12/16/13. -// Copyright 2013 High Fidelity, Inc. -// -// Declaration of version and build data +// Created by Leonardo Murillo on 8/13/15. +// Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/cmake/macros/IncludeApplicationVersion.cmake b/cmake/macros/IncludeApplicationVersion.cmake new file mode 100644 index 0000000000..96137294a7 --- /dev/null +++ b/cmake/macros/IncludeApplicationVersion.cmake @@ -0,0 +1,22 @@ +# +# IncludeApplicationVersion.cmake +# cmake/macros +# +# Created by Leonardo Murillo on 07/14/2015. +# Copyright 2015 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(INCLUDE_APPLICATION_VERSION) + if (DEFINED ENV{JOB_ID}) + set (BUILD_SEQ $ENV{JOB_ID}) + elseif (DEFINED ENV{ghprbPullId}) + set (BUILD_SEQ "PR: $ENV{ghprbPullId} - Commit: $ENV{ghprbActualCommit}") + else () + set(BUILD_SEQ "dev") + endif () + configure_file("${MACRO_DIR}/ApplicationVersion.h.in" "${PROJECT_BINARY_DIR}/includes/ApplicationVersion.h") + include_directories("${PROJECT_BINARY_DIR}/includes") +endmacro(INCLUDE_APPLICATION_VERSION) diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 41f2d15486..e4fa1d874d 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -31,4 +31,5 @@ include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") # append OpenSSL to our list of libraries to link target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) +include_application_version() copy_dlls_beside_windows_executable() diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 4e5b563fc6..abfd7efe89 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -75,6 +76,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); + setApplicationVersion(BUILD_VERSION); QSettings::setDefaultFormat(QSettings::IniFormat); // make sure we have a fresh AccountManager instance @@ -1478,7 +1480,6 @@ const char JSON_KEY_POOL[] = "pool"; const char JSON_KEY_PENDING_CREDITS[] = "pending_credits"; const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp"; const char JSON_KEY_USERNAME[] = "username"; - QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { QJsonObject nodeJson; @@ -1527,7 +1528,6 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { } const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment"; - QString pathForAssignmentScript(const QUuid& assignmentUUID) { QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION); newPath += "/scripts/"; @@ -1537,7 +1537,6 @@ QString pathForAssignmentScript(const QUuid& assignmentUUID) { } const QString URI_OAUTH = "/oauth"; - bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { const QString JSON_MIME_TYPE = "application/json"; @@ -2024,8 +2023,6 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl } const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token"; - - QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) { // pull the access token from the returned JSON and store it with the matching session UUID QJsonDocument returnedJSON = QJsonDocument::fromJson(tokenReply->readAll()); @@ -2042,7 +2039,6 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR } const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions"; - Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) { Headers cookieHeaders; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 9ad250bdcf..acafafa006 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -14,20 +14,12 @@ endforeach() find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistToolsMacros) -if (DEFINED ENV{JOB_ID}) - set(BUILD_SEQ $ENV{JOB_ID}) -elseif (DEFINED ENV{ghprbPullId}) - set(BUILD_SEQ "PR: $ENV{ghprbPullId} - Commit: $ENV{ghprbActualCommit}") -else () - set(BUILD_SEQ "dev") -endif () - if (WIN32) add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines endif() -configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVersion.h") +include_application_version() # grab the implementation and header files from src dirs file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") @@ -174,7 +166,7 @@ if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI AND APPLE) endif () # include headers for interface and InterfaceConfig. -include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes") +include_directories("${PROJECT_SOURCE_DIR}/src") target_link_libraries( ${TARGET_NAME} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d8dad73d82..fe361f8b8f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -52,6 +52,7 @@ #include #include +#include #include #include #include @@ -100,7 +101,6 @@ #include "AudioClient.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" -#include "InterfaceVersion.h" #include "LODManager.h" #include "Menu.h" #include "ModelPackager.h" diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 6b386ace92..d79e6bde58 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -28,4 +28,5 @@ include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES} ${TBB_LIBRARIES}) # append tbb includes to our list of includes to bubble -target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) \ No newline at end of file +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) +include_application_version() \ No newline at end of file diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 293d86475f..a7a6fe03ea 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -66,7 +66,9 @@ Assignment::Assignment(NLPacket& packet) : _payload(), _walletUUID() { + qDebug() << "LEOTEST: We are building an Assignment from a packet"; if (packet.getType() == PacketType::RequestAssignment) { + qDebug() << "LEOTEST: This is a request assignment packet"; _command = Assignment::RequestCommand; } else if (packet.getType() == PacketType::CreateAssignment) { _command = Assignment::CreateCommand; @@ -151,11 +153,16 @@ QDataStream& operator<<(QDataStream &out, const Assignment& assignment) { QDataStream& operator>>(QDataStream &in, Assignment& assignment) { quint8 packedType; in >> packedType; + if (assignment._command == Assignment::RequestCommand) { + qDebug() << "We are extracting the version"; + in >> assignment._nodeVersion; + } assignment._type = (Assignment::Type) packedType; in >> assignment._uuid >> assignment._pool >> assignment._payload; if (assignment._command == Assignment::RequestCommand) { + qDebug() << "LEOTEST: Operator for >> in case of RequestCommand"; in >> assignment._walletUUID; } diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index 67f861f850..f1b6cc7fba 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -98,6 +98,7 @@ protected: QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled QUuid _walletUUID; /// the UUID for the wallet that should be paid for this assignment + QString _nodeVersion; }; #endif // hifi_Assignment_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 44b9c0b829..9ecf31edab 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include "AccountManager.h" @@ -367,7 +368,6 @@ void NodeList::sendDSPathQuery(const QString& newPath) { } } - void NodeList::processDomainServerPathResponse(QSharedPointer packet) { // This is a response to a path query we theoretically made. // In the future we may want to check that this was actually from our DS and for a query we actually made. @@ -457,7 +457,6 @@ void NodeList::pingPunchForDomainServer() { } } - void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer packet) { if (_domainHandler.getSockAddr().isNull()) { // refuse to process this packet if we aren't currently connected to the DS @@ -547,9 +546,14 @@ void NodeList::sendAssignment(Assignment& assignment) { ? PacketType::CreateAssignment : PacketType::RequestAssignment; + qDebug() << "LEOTEST: Packet type name " << nameForPacketType(assignmentPacketType); auto assignmentPacket = NLPacket::create(assignmentPacketType); - + QDataStream packetStream(assignmentPacket.get()); + if (assignmentPacketType == PacketType::RequestAssignment) { + qDebug() << "LEOTEST: This is an assignment request, lets send the node version here " << BUILD_VERSION; + packetStream << BUILD_VERSION; + } packetStream << assignment; // TODO: should this be a non sourced packet? From 2756a0063877e2274a94df7a22ebcaaa8f9120d4 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 14:23:34 -0700 Subject: [PATCH 018/170] Get default avatar url from resources. --- interface/src/Application.cpp | 2 +- interface/src/avatar/Avatar.cpp | 4 ++-- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 12 +++++++++++- libraries/avatars/src/AvatarData.h | 8 +++++--- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5dac571045..914356fa45 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4405,7 +4405,7 @@ void Application::checkSkeleton() { msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); - _myAvatar->useFullAvatarURL(DEFAULT_FULL_AVATAR_MODEL_URL, DEFAULT_FULL_AVATAR_MODEL_NAME); + _myAvatar->useFullAvatarURL(AvatarData::defaultFullAvatarModelUrl(), DEFAULT_FULL_AVATAR_MODEL_NAME); } else { _physicsEngine.setCharacterController(_myAvatar->getCharacterController()); } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b537d5a5d8..893c9a9370 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -952,12 +952,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { void Avatar::setFaceModelURL(const QUrl& faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); - getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FULL_AVATAR_MODEL_URL, true, !isMyAvatar()); + getHead()->getFaceModel().setURL(_faceModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar()); } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); - _skeletonModel.setURL(_skeletonModelURL, DEFAULT_FULL_AVATAR_MODEL_URL, true, !isMyAvatar()); + _skeletonModel.setURL(_skeletonModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar()); } void Avatar::setAttachmentData(const QVector& attachmentData) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ddc0b0b9d1..6425169e57 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -721,7 +721,7 @@ void MyAvatar::loadData() { _targetScale = loadSetting(settings, "scale", 1.0f); setScale(_scale); - _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", DEFAULT_FULL_AVATAR_MODEL_URL).toUrl(); + _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl(); _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 78fa00eed7..afcc3a98b2 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -70,6 +70,16 @@ AvatarData::~AvatarData() { delete _referential; } +// We cannot have a file-level variable (const or otherwise) in the header if it uses PathUtils, because that references Application, which will not yet initialized. +// Thus we have a static class getter, referencing a static class var. +QUrl AvatarData::_defaultFullAvatarModelUrl = {}; // In C++, if this initialization were in the header, every file would have it's own copy, even for class vars. +const QUrl AvatarData::defaultFullAvatarModelUrl() { + if (_defaultFullAvatarModelUrl.isEmpty()) { + _defaultFullAvatarModelUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst"); + } + return _defaultFullAvatarModelUrl; +} + const glm::vec3& AvatarData::getPosition() const { if (_referential) { _referential->update(); @@ -941,7 +951,7 @@ void AvatarData::setFaceModelURL(const QUrl& faceModelURL) { } void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { - _skeletonModelURL = skeletonModelURL.isEmpty() ? DEFAULT_FULL_AVATAR_MODEL_URL : skeletonModelURL; + _skeletonModelURL = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 16003594c9..c4412e1d09 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -48,13 +48,13 @@ typedef unsigned long long quint64; #include #include -//FIXME #include #include #include #include "AABox.h" #include "HandData.h" #include "HeadData.h" +#include "PathUtils.h" #include "Player.h" #include "Recorder.h" #include "Referential.h" @@ -108,8 +108,7 @@ const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000; -//FIXME const QUrl DEFAULT_FULL_AVATAR_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst"); -const QUrl DEFAULT_FULL_AVATAR_MODEL_URL = QUrl("http://public.highfidelity.io/marketplace/contents/029db3d4-da2c-4cb2-9c08-b9612ba576f5/02949063e7c4aed42ad9d1a58461f56d.fst"); +// See also static AvatarData::defaultFullAvatarModelUrl(). const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); @@ -160,6 +159,8 @@ public: AvatarData(); virtual ~AvatarData(); + static const QUrl defaultFullAvatarModelUrl(); + virtual bool isMyAvatar() const { return false; } const QUuid& getSessionUUID() const { return _sessionUUID; } @@ -398,6 +399,7 @@ protected: SimpleMovingAverage _averageBytesReceived; private: + static QUrl _defaultFullAvatarModelUrl; // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); AvatarData& operator= (const AvatarData&); From a8a064990f64cd17257a0fdaeb6749571bacde23 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 14:42:37 -0700 Subject: [PATCH 019/170] apperance => appearance --- interface/src/ui/PreferencesDialog.cpp | 6 +++--- interface/ui/preferencesDialog.ui | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 59701e5cf3..8479f6778c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -47,8 +47,8 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser); connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, Application::getInstance(), &Application::loadDefaultScripts); - connect(ui.buttonChangeApperance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); - connect(ui.apperanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) { + connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); + connect(ui.appearanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) { this->fullAvatarURLChanged(url, ""); }); connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); @@ -61,7 +61,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : } void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) { - ui.apperanceDescription->setText(newValue); + ui.appearanceDescription->setText(newValue); const QString APPEARANCE_LABEL_TEXT("Appearance: "); ui.appearanceLabel->setText(APPEARANCE_LABEL_TEXT + modelName); DependencyManager::get()->getMyAvatar()->useFullAvatarURL(newValue, modelName); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index 71103da654..b91367dcf1 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -275,7 +275,7 @@ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - apperanceDescription + appearanceDescription @@ -283,12 +283,12 @@ - + 0 - + Arial @@ -297,7 +297,7 @@ - + Qt::Horizontal @@ -313,7 +313,7 @@ - + Arial From 74dc6a3dc54b53b541b99195a7f752bb422771e9 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 14:43:39 -0700 Subject: [PATCH 020/170] updateMotionBehavior => updateMotionBehaviorFromMenu --- interface/src/Menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 44f336e996..6d39326195 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -258,7 +258,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, - avatar, SLOT(updateMotionBehavior())); + avatar, SLOT(updateMotionBehaviorFromMenu())); MenuWrapper* viewMenu = addMenu("View"); addActionToQMenuAndActionHash(viewMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); From 7d4c96cc29bd5ab021f969bea213cf7daeaa2b42 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 14:53:12 -0700 Subject: [PATCH 021/170] avartar => avatar --- interface/src/avatar/MyAvatar.h | 2 +- interface/src/ui/PreferencesDialog.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0cd56ec99c..8ff9211101 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -127,7 +127,7 @@ public: Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } - Q_INVOKABLE const QString& getFullAvartarModelName() const { return _fullAvatarModelName; } + Q_INVOKABLE const QString& getFullAvatarModelName() const { return _fullAvatarModelName; } virtual void setAttachmentData(const QVector& attachmentData); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 8479f6778c..41b91d65c3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -128,7 +128,7 @@ void PreferencesDialog::loadPreferences() { ui.collisionSoundURLEdit->setText(myAvatar->getCollisionSoundURL()); fullAvatarURLChanged(myAvatar->getFullAvatarURLFromPreferences().toString(), - myAvatar->getFullAvartarModelName()); + myAvatar->getFullAvatarModelName()); ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger)); From 722c024f610aaf9ce558303e6bee1a2e594216e8 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 15:08:26 -0700 Subject: [PATCH 022/170] Remove obsolete code. --- interface/src/avatar/FaceModel.cpp | 56 ------------------------------ interface/src/avatar/FaceModel.h | 4 --- 2 files changed, 60 deletions(-) diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index e31c804185..0e8e5a6a91 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -47,66 +47,10 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { if (isActive()) { setOffset(-_geometry->getFBXGeometry().neckPivot); - - for (int i = 0; i < _rig->getJointStateCount(); i++) { - maybeUpdateNeckAndEyeRotation(i); - } - Model::simulateInternal(deltaTime); } } -void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const JointState& state, int index) { - // get the rotation axes in joint space and use them to adjust the rotation - glm::mat3 axes = glm::mat3_cast(glm::quat()); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * - glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) * - state.getPreTransform() * glm::mat4_cast(state.getPreRotation()))); - glm::vec3 pitchYawRoll = safeEulerAngles(_owningHead->getFinalOrientationInLocalFrame()); - glm::vec3 lean = glm::radians(glm::vec3(_owningHead->getFinalLeanForward(), - _owningHead->getTorsoTwist(), - _owningHead->getFinalLeanSideways())); - pitchYawRoll -= lean; - _rig->setJointRotationInConstrainedFrame(index, - glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2])) - * glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1])) - * glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0])) - * state.getDefaultRotation(), DEFAULT_PRIORITY); -} - -void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const JointState& state, int index) { - // likewise with the eye joints - // NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual. - glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() * - glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) * - state.getPreTransform() * glm::mat4_cast(state.getPreRotation() * state.getDefaultRotation())); - glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f)); - glm::vec3 lookAtDelta = _owningHead->getCorrectedLookAtPosition() - model->getTranslation(); - glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(lookAtDelta + glm::length(lookAtDelta) * _owningHead->getSaccade(), 1.0f)); - glm::quat between = rotationBetween(front, lookAt); - const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - _rig->setJointRotationInConstrainedFrame(index, glm::angleAxis(glm::clamp(glm::angle(between), - -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * - state.getDefaultRotation(), DEFAULT_PRIORITY); -} - -void FaceModel::maybeUpdateNeckAndEyeRotation(int index) { - const JointState& state = _rig->getJointState(index); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const int parentIndex = state.getParentIndex(); - - // guard against out-of-bounds access to _jointStates - if (parentIndex != -1 && parentIndex >= 0 && parentIndex < _rig->getJointStateCount()) { - const JointState& parentState = _rig->getJointState(parentIndex); - if (index == geometry.neckJointIndex) { - maybeUpdateNeckRotation(parentState, state, index); - - } else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) { - maybeUpdateEyeRotation(this, parentState, state, index); - } - } -} - bool FaceModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index ed1ea28508..5a19a8ea29 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -26,10 +26,6 @@ public: virtual void simulate(float deltaTime, bool fullUpdate = true); - void maybeUpdateNeckRotation(const JointState& parentState, const JointState& state, int index); - void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const JointState& state, int index); - void maybeUpdateNeckAndEyeRotation(int index); - /// Retrieve the positions of up to two eye meshes. /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; From 7c4813f1cf278a58d4fae85ad6bfbc1b1368b189 Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Thu, 13 Aug 2015 16:35:55 -0600 Subject: [PATCH 023/170] Another versioning checkpoint + cleanup --- domain-server/src/DomainServer.cpp | 6 ++++- domain-server/src/DomainServerNodeData.h | 5 ++++ domain-server/src/PendingAssignedNodeData.cpp | 5 ++-- domain-server/src/PendingAssignedNodeData.h | 6 ++++- libraries/networking/src/Assignment.cpp | 26 ++++++++++++------- libraries/networking/src/Assignment.h | 2 ++ libraries/networking/src/NodeList.cpp | 5 ---- 7 files changed, 37 insertions(+), 18 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index abfd7efe89..071626ef1e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -740,6 +740,7 @@ void DomainServer::processConnectRequestPacket(QSharedPointer packet) if (isAssignment) { nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID()); + nodeData->setNodeVersion(pendingAssigneeData->getNodeVersion()); // always allow assignment clients to create and destroy entities newNode->setCanAdjustLocks(true); @@ -1170,7 +1171,8 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer packe // add the information for that deployed assignment to the hash of pending assigned nodes PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(), - requestAssignment.getWalletUUID()); + requestAssignment.getWalletUUID(), + requestAssignment.getNodeVersion()); _pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData); } else { if (requestAssignment.getType() != Assignment::AgentType @@ -1480,6 +1482,7 @@ const char JSON_KEY_POOL[] = "pool"; const char JSON_KEY_PENDING_CREDITS[] = "pending_credits"; const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp"; const char JSON_KEY_USERNAME[] = "username"; +const char JSON_KEY_VERSION[] = "version"; QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { QJsonObject nodeJson; @@ -1506,6 +1509,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { // add the node username, if it exists nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername(); + nodeJson[JSON_KEY_VERSION] = nodeData->getNodeVersion(); SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID()); if (matchingAssignment) { diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 57ed96acab..2c57368653 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -50,6 +50,10 @@ public: const NodeSet& getNodeInterestSet() const { return _nodeInterestSet; } void setNodeInterestSet(const NodeSet& nodeInterestSet) { _nodeInterestSet = nodeInterestSet; } + + void setNodeVersion(const QString& nodeVersion) { _nodeVersion = nodeVersion; } + const QString& getNodeVersion() { return _nodeVersion; } + private: QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); @@ -62,6 +66,7 @@ private: HifiSockAddr _sendingSockAddr; bool _isAuthenticated; NodeSet _nodeInterestSet; + QString _nodeVersion; }; #endif // hifi_DomainServerNodeData_h diff --git a/domain-server/src/PendingAssignedNodeData.cpp b/domain-server/src/PendingAssignedNodeData.cpp index 21b3aa4ca4..30310ac01f 100644 --- a/domain-server/src/PendingAssignedNodeData.cpp +++ b/domain-server/src/PendingAssignedNodeData.cpp @@ -11,9 +11,10 @@ #include "PendingAssignedNodeData.h" -PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID) : +PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID, const QString& nodeVersion) : _assignmentUUID(assignmentUUID), - _walletUUID(walletUUID) + _walletUUID(walletUUID), + _nodeVersion(nodeVersion) { } \ No newline at end of file diff --git a/domain-server/src/PendingAssignedNodeData.h b/domain-server/src/PendingAssignedNodeData.h index 93d99a6f8f..2d546f573f 100644 --- a/domain-server/src/PendingAssignedNodeData.h +++ b/domain-server/src/PendingAssignedNodeData.h @@ -18,16 +18,20 @@ class PendingAssignedNodeData : public QObject { Q_OBJECT public: - PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID); + PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID, const QString& nodeVersion); void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } const QUuid& getWalletUUID() const { return _walletUUID; } + + const QString& getNodeVersion() const { return _nodeVersion; } + private: QUuid _assignmentUUID; QUuid _walletUUID; + QString _nodeVersion; }; #endif // hifi_PendingAssignedNodeData_h \ No newline at end of file diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index a7a6fe03ea..8a97b7cf8c 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -13,8 +13,10 @@ #include "SharedUtil.h" #include "UUID.h" +#include #include +#include #include "Assignment.h" Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { @@ -52,11 +54,14 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const _location(location), _payload(), _isStatic(false), - _walletUUID() + _walletUUID(), + _nodeVersion() { if (_command == Assignment::CreateCommand) { // this is a newly created assignment, generate a random UUID _uuid = QUuid::createUuid(); + } else if (_command == Assignment::RequestCommand) { + _nodeVersion = BUILD_VERSION; } } @@ -64,11 +69,10 @@ Assignment::Assignment(NLPacket& packet) : _pool(), _location(GlobalLocation), _payload(), - _walletUUID() + _walletUUID(), + _nodeVersion() { - qDebug() << "LEOTEST: We are building an Assignment from a packet"; if (packet.getType() == PacketType::RequestAssignment) { - qDebug() << "LEOTEST: This is a request assignment packet"; _command = Assignment::RequestCommand; } else if (packet.getType() == PacketType::CreateAssignment) { _command = Assignment::CreateCommand; @@ -85,15 +89,14 @@ Assignment::Assignment(NLPacket& packet) : Assignment::Assignment(const Assignment& otherAssignment) { - _uuid = otherAssignment._uuid; - _command = otherAssignment._command; _type = otherAssignment._type; _location = otherAssignment._location; _pool = otherAssignment._pool; _payload = otherAssignment._payload; _walletUUID = otherAssignment._walletUUID; + _nodeVersion = otherAssignment._nodeVersion; } Assignment& Assignment::operator=(const Assignment& rhsAssignment) { @@ -112,6 +115,7 @@ void Assignment::swap(Assignment& otherAssignment) { swap(_pool, otherAssignment._pool); swap(_payload, otherAssignment._payload); swap(_walletUUID, otherAssignment._walletUUID); + swap(_nodeVersion, otherAssignment._nodeVersion); } const char* Assignment::getTypeName() const { @@ -141,7 +145,13 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) { } QDataStream& operator<<(QDataStream &out, const Assignment& assignment) { - out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload; + out << (quint8) assignment._type; + + if (assignment._command == Assignment::RequestCommand) { + out << assignment._nodeVersion; + } + + out << assignment._uuid << assignment._pool << assignment._payload; if (assignment._command == Assignment::RequestCommand) { out << assignment._walletUUID; @@ -154,7 +164,6 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) { quint8 packedType; in >> packedType; if (assignment._command == Assignment::RequestCommand) { - qDebug() << "We are extracting the version"; in >> assignment._nodeVersion; } assignment._type = (Assignment::Type) packedType; @@ -162,7 +171,6 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) { in >> assignment._uuid >> assignment._pool >> assignment._payload; if (assignment._command == Assignment::RequestCommand) { - qDebug() << "LEOTEST: Operator for >> in case of RequestCommand"; in >> assignment._walletUUID; } diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index f1b6cc7fba..6950223a9d 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -83,6 +83,8 @@ public: void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } const QUuid& getWalletUUID() const { return _walletUUID; } + const QString& getNodeVersion() const { return _nodeVersion; } + const char* getTypeName() const; friend QDebug operator<<(QDebug debug, const Assignment& assignment); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 9ecf31edab..563b7d7080 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -546,14 +546,9 @@ void NodeList::sendAssignment(Assignment& assignment) { ? PacketType::CreateAssignment : PacketType::RequestAssignment; - qDebug() << "LEOTEST: Packet type name " << nameForPacketType(assignmentPacketType); auto assignmentPacket = NLPacket::create(assignmentPacketType); QDataStream packetStream(assignmentPacket.get()); - if (assignmentPacketType == PacketType::RequestAssignment) { - qDebug() << "LEOTEST: This is an assignment request, lets send the node version here " << BUILD_VERSION; - packetStream << BUILD_VERSION; - } packetStream << assignment; // TODO: should this be a non sourced packet? From b345d75e4d897f3b20f43de2705f6586aa04d9c4 Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Thu, 13 Aug 2015 16:44:28 -0600 Subject: [PATCH 024/170] More cleanup and showing version in ds web gui --- domain-server/resources/web/index.shtml | 4 +++- libraries/networking/src/Assignment.cpp | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/index.shtml b/domain-server/resources/web/index.shtml index b159d624a4..0f720ebe79 100644 --- a/domain-server/resources/web/index.shtml +++ b/domain-server/resources/web/index.shtml @@ -9,6 +9,7 @@ Type + Version UUID Pool Username @@ -24,6 +25,7 @@ <% _.each(nodes, function(node, node_index){ %> <%- node.type %> + <%- node.version %> <%- node.uuid %> <%- node.pool %> <%- node.username %> @@ -75,4 +77,4 @@ - \ No newline at end of file + diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 8a97b7cf8c..b63373858e 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -13,7 +13,6 @@ #include "SharedUtil.h" #include "UUID.h" -#include #include #include From 63608d8012c2c64c98ec8cc4ce4d9020a4675a3a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 15:55:43 -0700 Subject: [PATCH 025/170] Undo avatar choice if the whole preferences dialog is cancelled. --- interface/src/ui/PreferencesDialog.cpp | 23 ++++++++++++++++++++--- interface/src/ui/PreferencesDialog.h | 4 ++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 41b91d65c3..63170fdd70 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -68,12 +68,24 @@ void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QStr } void PreferencesDialog::accept() { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + _lastGoodAvatarURL = myAvatar->getFullAvatarURLFromPreferences(); + _lastGoodAvatarName = myAvatar->getFullAvatarModelName(); savePreferences(); close(); delete _marketplaceWindow; _marketplaceWindow = NULL; } +void PreferencesDialog::restoreLastGoodAvatar() { + fullAvatarURLChanged(_lastGoodAvatarURL.toString(), _lastGoodAvatarName); +} + +void PreferencesDialog::reject() { + restoreLastGoodAvatar(); + QDialog::reject(); +} + void PreferencesDialog::openSnapshotLocationBrowser() { QString dir = QFileDialog::getExistingDirectory(this, tr("Snapshots Location"), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation), @@ -127,8 +139,9 @@ void PreferencesDialog::loadPreferences() { ui.collisionSoundURLEdit->setText(myAvatar->getCollisionSoundURL()); - fullAvatarURLChanged(myAvatar->getFullAvatarURLFromPreferences().toString(), - myAvatar->getFullAvatarModelName()); + _lastGoodAvatarURL = myAvatar->getFullAvatarURLFromPreferences(); + _lastGoodAvatarName = myAvatar->getFullAvatarModelName(); + fullAvatarURLChanged(_lastGoodAvatarURL.toString(), _lastGoodAvatarName); ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger)); @@ -211,7 +224,11 @@ void PreferencesDialog::savePreferences() { } myAvatar->setCollisionSoundURL(ui.collisionSoundURLEdit->text()); - // avatar model url is already persisted by fullAvatarURLChanged() + + // MyAvatar persists its own data. If it doesn't agree with what the user has explicitly accepted, set it back to old values. + if (_lastGoodAvatarURL != myAvatar->getFullAvatarURLFromPreferences()) { + restoreLastGoodAvatar(); + } if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger) != ui.sendDataCheckBox->isChecked()) { diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index 47341b8feb..6d7a87b97c 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -33,6 +33,9 @@ protected: private: void loadPreferences(); void savePreferences(); + QUrl _lastGoodAvatarURL; + QString _lastGoodAvatarName; + void restoreLastGoodAvatar(); Ui_PreferencesDialog ui; @@ -42,6 +45,7 @@ private: private slots: void accept(); + void reject(); void openFullAvatarModelBrowser(); void openSnapshotLocationBrowser(); void openScriptsLocationBrowser(); From f00d3c037d99584892dcc468cd3b2509ae45205a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 13 Aug 2015 17:36:03 -0700 Subject: [PATCH 026/170] whitespace --- interface/src/ui/PreferencesDialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 63170fdd70..a6340d3955 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -48,9 +48,9 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, Application::getInstance(), &Application::loadDefaultScripts); connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); - connect(ui.appearanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) { - this->fullAvatarURLChanged(url, ""); - }); + connect(ui.appearanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) { + this->fullAvatarURLChanged(url, ""); + }); connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); // move dialog to left side From daeb90c1e0644c30d9f4338972a7e36bb7dd1a86 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 14 Aug 2015 09:35:39 -0700 Subject: [PATCH 027/170] fix clamp in AddEntityOperator --- libraries/entities/src/AddEntityOperator.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/entities/src/AddEntityOperator.cpp b/libraries/entities/src/AddEntityOperator.cpp index db9a18a3e9..51015c1ff4 100644 --- a/libraries/entities/src/AddEntityOperator.cpp +++ b/libraries/entities/src/AddEntityOperator.cpp @@ -26,8 +26,7 @@ AddEntityOperator::AddEntityOperator(EntityTree* tree, // caller must have verified existence of newEntity assert(_newEntity); - // FIXME - how does this change for new origin??? - _newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, (float)TREE_SCALE); + _newEntityBox = _newEntity->getMaximumAACube().clamp((float)(-HALF_TREE_SCALE), (float)HALF_TREE_SCALE); } bool AddEntityOperator::preRecursion(OctreeElement* element) { From bdc78e9666a6fe95f9b931320f0571cd8d7896d6 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 14 Aug 2015 10:16:48 -0700 Subject: [PATCH 028/170] some CR cleanup --- libraries/entities/src/EntitySimulation.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index c614b62c91..06f5023193 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -113,8 +113,7 @@ void EntitySimulation::sortEntitiesThatMoved() { // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. PerformanceTimer perfTimer("sortingEntities"); MovingEntitiesOperator moveOperator(_entityTree); - AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE), - (float)TREE_SCALE); + AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); SetOfEntities::iterator itemItr = _entitiesToSort.begin(); while (itemItr != _entitiesToSort.end()) { EntityItemPointer entity = *itemItr; @@ -196,8 +195,7 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & EntityItem::DIRTY_POSITION) { - AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE, (float)-HALF_TREE_SCALE), - (float)TREE_SCALE); + AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); AACube newCube = entity->getMaximumAACube(); if (!domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; From 7c6a9b131d581644400879658d280435a11cfb22 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 14 Aug 2015 10:17:48 -0700 Subject: [PATCH 029/170] some CR cleanup --- interface/src/Util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index f62a6b8fc5..5a9ecfee67 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -44,7 +44,7 @@ void renderWorldBox(gpu::Batch& batch) { auto transform = Transform{}; batch.setModelTransform(transform); - // FIXME - new origin tweaks need to be done to this + // TODO - consider alternate rendering for negative build-able space in the domain geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(TREE_SCALE, 0.0f, 0.0f), red); geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, TREE_SCALE, 0.0f), green); geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, TREE_SCALE), blue); From 44fcf71f075bfc3136c0e2c99f6beba523555227 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 14 Aug 2015 15:26:49 -0700 Subject: [PATCH 030/170] change domain to be 32k and still have origin centered at 0 --- examples/edit.js | 2 +- libraries/entities/src/EntityItem.cpp | 13 ++----------- libraries/entities/src/EntityItem.h | 1 - libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 - libraries/octree/src/OctreeConstants.h | 2 +- 6 files changed, 5 insertions(+), 16 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index 4cc38a0c3d..aea08640aa 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1020,7 +1020,7 @@ function getPositionToCreateEntity() { var cameraPosition = Camera.position; - var HALF_TREE_SCALE = 16384 / 2; + var HALF_TREE_SCALE = 16384; var cameraOutOfBounds = cameraPosition.x < -HALF_TREE_SCALE || cameraPosition.y < -HALF_TREE_SCALE || cameraPosition.z < -HALF_TREE_SCALE; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 922170707b..0ffcc00ead 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -634,11 +634,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // but since we're using macros below we have to temporarily modify overwriteLocalData. bool oldOverwrite = overwriteLocalData; overwriteLocalData = overwriteLocalData && !weOwnSimulation; - if (args.bitstreamVersion >= VERSION_ENTITIES_CENTER_ORIGIN) { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); - } else { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionOldOrigin); - } + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); @@ -660,7 +656,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } else { // legacy order of packing here // TODO: purge this logic in a few months from now (2015.07) - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionOldOrigin); + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); @@ -1321,11 +1317,6 @@ void EntityItem::updatePosition(const glm::vec3& value) { } } -void EntityItem::updatePositionOldOrigin(const glm::vec3& value) { - glm::vec3 newValue = value - glm::vec3(HALF_TREE_SCALE); - updatePosition(newValue); -} - void EntityItem::updateDimensions(const glm::vec3& value) { auto delta = glm::distance(getDimensions(), value); if (delta > IGNORE_DIMENSIONS_DELTA) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 936b2e4d21..e420d08709 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -363,7 +363,6 @@ public: // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags void updatePosition(const glm::vec3& value); - void updatePositionOldOrigin(const glm::vec3& value); void updateDimensions(const glm::vec3& value); void updateRotation(const glm::quat& rotation); void updateDensity(float value); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index a1af3c58c5..5562490344 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType::Value packetType) { case EntityAdd: case EntityEdit: case EntityData: - return VERSION_ENTITIES_CENTER_ORIGIN; + return VERSION_ENTITIES_POLYLINE; case AvatarData: return 12; default: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 613ea4913d..1aeadb1af9 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -142,6 +142,5 @@ const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE const PacketVersion VERSION_ENTITIES_NEW_PROTOCOL_LAYER = 35; const PacketVersion VERSION_POLYVOX_TEXTURES = 36; const PacketVersion VERSION_ENTITIES_POLYLINE = 37; -const PacketVersion VERSION_ENTITIES_CENTER_ORIGIN = 38; #endif // hifi_PacketHeaders_h \ No newline at end of file diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index acfa1d14c6..a98f042006 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -17,7 +17,7 @@ const quint64 CHANGE_FUDGE = 1000 * 200; // useconds of fudge in determining if we want to resend changed voxels -const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe +const int TREE_SCALE = 32768; // ~20 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe const int HALF_TREE_SCALE = TREE_SCALE / 2; // This controls the LOD. Larger number will make smaller voxels visible at greater distance. From 65864b1876e4f1843c853f3e5ae28bbb5ce3bb48 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 14 Aug 2015 15:30:16 -0700 Subject: [PATCH 031/170] only draw axis for HALF_TREE_SCALE --- interface/src/Util.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 5a9ecfee67..8f687a93d6 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -45,11 +45,11 @@ void renderWorldBox(gpu::Batch& batch) { batch.setModelTransform(transform); // TODO - consider alternate rendering for negative build-able space in the domain - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(TREE_SCALE, 0.0f, 0.0f), red); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, TREE_SCALE, 0.0f), green); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, TREE_SCALE), blue); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, TREE_SCALE), glm::vec3(TREE_SCALE, 0.0f, TREE_SCALE), grey); - geometryCache->renderLine(batch, glm::vec3(TREE_SCALE, 0.0f, TREE_SCALE), glm::vec3(TREE_SCALE, 0.0f, 0.0f), grey); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), red); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, HALF_TREE_SCALE, 0.0f), green); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), blue); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), grey); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), grey); // Draw meter markers along the 3 axis to help with measuring things const float MARKER_DISTANCE = 1.0f; From 27e5322cb4275276efb0828865553188e1491754 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 15 Aug 2015 11:25:27 -0700 Subject: [PATCH 032/170] compute polyvox collision hull differently for marching-cube surfaces --- .../src/RenderablePolyVoxEntityItem.cpp | 132 ++++++++++++------ 1 file changed, 91 insertions(+), 41 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 23093b9f3f..be2d9b8fe6 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -40,7 +40,7 @@ #include "EntityTreeRenderer.h" #include "polyvox_vert.h" #include "polyvox_frag.h" -#include "RenderablePolyVoxEntityItem.h" +#include "RenderablePolyVoxEntityItem.h" gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; @@ -553,53 +553,103 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { } _points.clear(); - unsigned int i = 0; - - glm::mat4 wToM = voxelToLocalMatrix(); - AABox box; + glm::mat4 vtoM = voxelToLocalMatrix(); - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - if (getVoxel(x, y, z) > 0) { - QVector pointsInPart; + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES) { + unsigned int i = 0; + /* pull top-facing triangles into polyhedrons so they can be walked on */ + const model::MeshPointer& mesh = _modelGeometry.getMesh(); + const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); + const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); + gpu::BufferView::Iterator it = indexBufferView.cbegin(); + while (it != indexBufferView.cend()) { + uint32_t p0Index = *(it++); + uint32_t p1Index = *(it++); + uint32_t p2Index = *(it++); - float offL = -0.5f; - float offH = 0.5f; + const glm::vec3 p0 = vertexBufferView.get(p0Index); + const glm::vec3 p1 = vertexBufferView.get(p1Index); + const glm::vec3 p2 = vertexBufferView.get(p2Index); - glm::vec3 p000 = glm::vec3(wToM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); - glm::vec3 p001 = glm::vec3(wToM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); - glm::vec3 p010 = glm::vec3(wToM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); - glm::vec3 p011 = glm::vec3(wToM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); - glm::vec3 p100 = glm::vec3(wToM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); - glm::vec3 p101 = glm::vec3(wToM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); - glm::vec3 p110 = glm::vec3(wToM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); - glm::vec3 p111 = glm::vec3(wToM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); + qDebug() << p0Index << p1Index << p2Index; + qDebug() << (int)p0.x << (int)p0.y << (int)p0.z; - box += p000; - box += p001; - box += p010; - box += p011; - box += p100; - box += p101; - box += p110; - box += p111; + glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face + glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); + float threshold = 1.0f / sqrtf(3.0f); + if (normal.y > -threshold && normal.y < threshold) { + // this triangle is more a wall than a floor, skip it. + } else { + float dropAmount = 2.0f; // XXX magic + glm::vec3 p3 = av - glm::vec3(0.0f, dropAmount, 0.0f); - pointsInPart << p000; - pointsInPart << p001; - pointsInPart << p010; - pointsInPart << p011; - pointsInPart << p100; - pointsInPart << p101; - pointsInPart << p110; - pointsInPart << p111; + glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); + glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); + glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); + glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); - // add next convex hull - QVector newMeshPoints; - _points << newMeshPoints; - // add points to the new convex hull - _points[i++] << pointsInPart; + box += p0Model; + box += p1Model; + box += p2Model; + box += p3Model; + + QVector pointsInPart; + pointsInPart << p0Model; + pointsInPart << p1Model; + pointsInPart << p2Model; + pointsInPart << p3Model; + // add next convex hull + QVector newMeshPoints; + _points << newMeshPoints; + // add points to the new convex hull + _points[i++] << pointsInPart; + } + } + } else { + unsigned int i = 0; + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + if (getVoxel(x, y, z) > 0) { + QVector pointsInPart; + + float offL = -0.5f; + float offH = 0.5f; + + glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); + glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); + glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); + glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); + glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); + glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); + glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); + glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); + + box += p000; + box += p001; + box += p010; + box += p011; + box += p100; + box += p101; + box += p110; + box += p111; + + pointsInPart << p000; + pointsInPart << p001; + pointsInPart << p010; + pointsInPart << p011; + pointsInPart << p100; + pointsInPart << p101; + pointsInPart << p110; + pointsInPart << p111; + + // add next convex hull + QVector newMeshPoints; + _points << newMeshPoints; + // add points to the new convex hull + _points[i++] << pointsInPart; + } } } } From 79a7755cdbc762163394195d5de829ba2ba0405f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 15 Aug 2015 12:18:36 -0700 Subject: [PATCH 033/170] add SURFACE_EDGED_MARCHING_CUBES polyvox surface extraction style --- examples/html/entityProperties.html | 1 + .../src/RenderablePolyVoxEntityItem.cpp | 21 +++++++++++-------- libraries/entities/src/PolyVoxEntityItem.h | 3 ++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index a2358b40d5..ad489afddf 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -1072,6 +1072,7 @@ + diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index be2d9b8fe6..07026f073c 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -78,6 +78,7 @@ bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem:: return true; case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: if (x < 0 || y < 0 || z < 0 || x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) { return false; @@ -112,7 +113,7 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) _onCount = 0; - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC) { + if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This // changes how the surface extractor acts -- mainly it becomes impossible to have holes in the // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the @@ -156,8 +157,7 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) void RenderablePolyVoxEntityItem::updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { // if we are switching to or from "edged" we need to force a resize of _volData. - if (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || - _voxelSurfaceStyle == SURFACE_EDGED_CUBIC) { + if (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { if (_volData) { delete _volData; } @@ -182,11 +182,11 @@ glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units switch (_voxelSurfaceStyle) { case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: - return scale / 2.0f; - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - return scale / -2.0f; case PolyVoxEntityItem::SURFACE_CUBIC: return scale / 2.0f; + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + return scale / -2.0f; } return glm::vec3(0.0f, 0.0f, 0.0f); } @@ -223,7 +223,7 @@ uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { // voxels all around the requested voxel space. Having the empty voxels around // the edges changes how the surface extractor behaves. - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC) { + if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { return _volData->getVoxelAt(x + 1, y + 1, z + 1); } return _volData->getVoxelAt(x, y, z); @@ -238,7 +238,7 @@ void RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t updateOnCount(x, y, z, toValue); - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC) { + if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); } else { _volData->setVoxelAt(x, y, z, toValue); @@ -399,6 +399,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o glm::vec4 result = callback._result; switch (_voxelSurfaceStyle) { case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: result -= glm::vec4(1, 1, 1, 0); // compensate for the extra voxel border break; case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: @@ -556,7 +557,8 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { AABox box; glm::mat4 vtoM = voxelToLocalMatrix(); - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES) { + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { unsigned int i = 0; /* pull top-facing triangles into polyhedrons so they can be walked on */ const model::MeshPointer& mesh = _modelGeometry.getMesh(); @@ -683,6 +685,7 @@ void RenderablePolyVoxEntityItem::getModel() { PolyVox::SurfaceMesh polyVoxMesh; switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 0d0ab060f9..29d0b2e595 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -58,7 +58,8 @@ class PolyVoxEntityItem : public EntityItem { enum PolyVoxSurfaceStyle { SURFACE_MARCHING_CUBES, SURFACE_CUBIC, - SURFACE_EDGED_CUBIC + SURFACE_EDGED_CUBIC, + SURFACE_EDGED_MARCHING_CUBES }; void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle); From 09a9deabdbfd3a19038135f145da2746b1a65781 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 15 Aug 2015 12:55:24 -0700 Subject: [PATCH 034/170] compute polyvox collision hull differently for marching-cube surfaces --- .../src/RenderablePolyVoxEntityItem.cpp | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 07026f073c..b803e32e1e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -43,6 +43,7 @@ #include "RenderablePolyVoxEntityItem.h" gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; +const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); @@ -574,39 +575,30 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { const glm::vec3 p1 = vertexBufferView.get(p1Index); const glm::vec3 p2 = vertexBufferView.get(p2Index); - qDebug() << p0Index << p1Index << p2Index; - qDebug() << (int)p0.x << (int)p0.y << (int)p0.z; - glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); - float threshold = 1.0f / sqrtf(3.0f); - if (normal.y > -threshold && normal.y < threshold) { - // this triangle is more a wall than a floor, skip it. - } else { - float dropAmount = 2.0f; // XXX magic - glm::vec3 p3 = av - glm::vec3(0.0f, dropAmount, 0.0f); + glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; - glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); - glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); - glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); - glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); + glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); + glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); + glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); + glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); - box += p0Model; - box += p1Model; - box += p2Model; - box += p3Model; + box += p0Model; + box += p1Model; + box += p2Model; + box += p3Model; - QVector pointsInPart; - pointsInPart << p0Model; - pointsInPart << p1Model; - pointsInPart << p2Model; - pointsInPart << p3Model; - // add next convex hull - QVector newMeshPoints; - _points << newMeshPoints; - // add points to the new convex hull - _points[i++] << pointsInPart; - } + QVector pointsInPart; + pointsInPart << p0Model; + pointsInPart << p1Model; + pointsInPart << p2Model; + pointsInPart << p3Model; + // add next convex hull + QVector newMeshPoints; + _points << newMeshPoints; + // add points to the new convex hull + _points[i++] << pointsInPart; } } else { unsigned int i = 0; @@ -790,8 +782,6 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { _zTexture = DependencyManager::get()->getTexture(_zTextureURL); } - batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - if (_xTexture) { batch.setResourceTexture(0, _xTexture->getGPUTexture()); } else { From 708190bfd6cefc0f746a3746c703fe756fc675ea Mon Sep 17 00:00:00 2001 From: Christopher Root Date: Sat, 15 Aug 2015 14:10:25 -0700 Subject: [PATCH 035/170] horiz amb occl performance upgrades --- .../src/AmbientOcclusionEffect.cpp | 53 ++++++-- .../render-utils/src/AmbientOcclusionEffect.h | 11 +- .../render-utils/src/ambient_occlusion.slf | 126 +++++++++++++----- 3 files changed, 142 insertions(+), 48 deletions(-) diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 5f0afd37d1..b94bd09538 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -51,8 +51,13 @@ const gpu::PipelinePointer& AmbientOcclusion::getOcclusionPipeline() { _gBiasLoc = program->getUniforms().findLocation("g_bias"); _gSampleRadiusLoc = program->getUniforms().findLocation("g_sample_rad"); _gIntensityLoc = program->getUniforms().findLocation("g_intensity"); - _bufferWidthLoc = program->getUniforms().findLocation("bufferWidth"); - _bufferHeightLoc = program->getUniforms().findLocation("bufferHeight"); + + _nearLoc = program->getUniforms().findLocation("near"); + _depthScaleLoc = program->getUniforms().findLocation("depthScale"); + _depthTexCoordOffsetLoc = program->getUniforms().findLocation("depthTexCoordOffset"); + _depthTexCoordScaleLoc = program->getUniforms().findLocation("depthTexCoordScale"); + _renderTargetResLoc = program->getUniforms().findLocation("renderTargetRes"); + _renderTargetResInvLoc = program->getUniforms().findLocation("renderTargetResInv"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -172,9 +177,19 @@ const gpu::PipelinePointer& AmbientOcclusion::getBlendPipeline() { void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(renderContext->args); assert(renderContext->args->_viewFrustum); - RenderArgs* args = renderContext->args; gpu::Batch batch; + RenderArgs* args = renderContext->args; + + auto framebufferCache = DependencyManager::get(); + QSize framebufferSize = framebufferCache->getFrameBufferSize(); + float fbWidth = framebufferSize.width(); + float fbHeight = framebufferSize.height(); + float sMin = args->_viewport.x / fbWidth; + float sWidth = args->_viewport.z / fbWidth; + float tMin = args->_viewport.y / fbHeight; + float tHeight = args->_viewport.w / fbHeight; + glm::mat4 projMat; Transform viewMat; @@ -186,8 +201,8 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons // Occlusion step getOcclusionPipeline(); - batch.setResourceTexture(0, DependencyManager::get()->getPrimaryDepthTexture()); - batch.setResourceTexture(1, DependencyManager::get()->getPrimaryNormalTexture()); + batch.setResourceTexture(0, framebufferCache->getPrimaryDepthTexture()); + batch.setResourceTexture(1, framebufferCache->getPrimaryNormalTexture()); _occlusionBuffer->setRenderBuffer(0, _occlusionTexture); batch.setFramebuffer(_occlusionBuffer); @@ -203,8 +218,28 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons batch._glUniform1f(_gBiasLoc, g_bias); batch._glUniform1f(_gSampleRadiusLoc, g_sample_rad); batch._glUniform1f(_gIntensityLoc, g_intensity); - batch._glUniform1f(_bufferWidthLoc, DependencyManager::get()->getFrameBufferSize().width()); - batch._glUniform1f(_bufferHeightLoc, DependencyManager::get()->getFrameBufferSize().height()); + + // setup uniforms for extracting depth from the depth buffer and + // converting that depth to a camera-space position, same as DeferredLightingEffect.cpp + float left, right, bottom, top, nearVal, farVal; + glm::vec4 nearClipPlane, farClipPlane; + args->_viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); + + batch._glUniform1f(_nearLoc, nearVal); + + float depthScale = (farVal - nearVal) / farVal; + batch._glUniform1f(_depthScaleLoc, depthScale); + + float nearScale = -1.0f / nearVal; + float depthTexCoordScaleS = (right - left) * nearScale / sWidth; + float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; + float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; + float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; + batch._glUniform2f(_depthTexCoordOffsetLoc, depthTexCoordOffsetS, depthTexCoordOffsetT); + batch._glUniform2f(_depthTexCoordScaleLoc, depthTexCoordScaleS, depthTexCoordScaleT); + + batch._glUniform2f(_renderTargetResLoc, fbWidth, fbHeight); + batch._glUniform2f(_renderTargetResInvLoc, 1.0/fbWidth, 1.0/fbHeight); glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); glm::vec2 bottomLeft(-1.0f, -1.0f); @@ -238,13 +273,13 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons // Blend step getBlendPipeline(); batch.setResourceTexture(0, _hBlurTexture); - batch.setFramebuffer(DependencyManager::get()->getPrimaryFramebuffer()); + batch.setFramebuffer(framebufferCache->getPrimaryFramebuffer()); // Bind the fourth gpu::Pipeline we need - for blending the primary color buffer with blurred occlusion texture batch.setPipeline(getBlendPipeline()); DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); - + // Ready to render args->_context->syncCache(); args->_context->render((batch)); diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index 0b695dd2ad..6153795ea6 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -36,8 +36,15 @@ private: gpu::int32 _gBiasLoc; gpu::int32 _gSampleRadiusLoc; gpu::int32 _gIntensityLoc; - gpu::int32 _bufferWidthLoc; - gpu::int32 _bufferHeightLoc; + + gpu::int32 _nearLoc; + gpu::int32 _depthScaleLoc; + gpu::int32 _depthTexCoordOffsetLoc; + gpu::int32 _depthTexCoordScaleLoc; + gpu::int32 _renderTargetResLoc; + gpu::int32 _renderTargetResInvLoc; + + float g_scale; float g_bias; float g_sample_rad; diff --git a/libraries/render-utils/src/ambient_occlusion.slf b/libraries/render-utils/src/ambient_occlusion.slf index 649fb16c56..73bed26a9c 100644 --- a/libraries/render-utils/src/ambient_occlusion.slf +++ b/libraries/render-utils/src/ambient_occlusion.slf @@ -30,25 +30,49 @@ uniform float g_scale; uniform float g_bias; uniform float g_sample_rad; uniform float g_intensity; -uniform float bufferWidth; -uniform float bufferHeight; + +// the distance to the near clip plane +uniform float near; + +// scale factor for depth: (far - near) / far +uniform float depthScale; + +// offset for depth texture coordinates +uniform vec2 depthTexCoordOffset; + +// scale for depth texture coordinates +uniform vec2 depthTexCoordScale; + +// the resolution of the occlusion buffer +// and its inverse +uniform vec2 renderTargetRes; +uniform vec2 renderTargetResInv; + + const float PI = 3.14159265; -const vec2 FocalLen = vec2(1.0, 1.0); +// const vec2 FocalLen = vec2(1.0, 1.0); +// const vec2 LinMAD = vec2(0.1-10.0, 0.1+10.0) / (2.0*0.1*10.0); -const vec2 LinMAD = vec2(0.1-10.0, 0.1+10.0) / (2.0*0.1*10.0); - -const vec2 AORes = vec2(1024.0, 768.0); -const vec2 InvAORes = vec2(1.0/1024.0, 1.0/768.0); -const vec2 NoiseScale = vec2(1024.0, 768.0) / 4.0; +// const vec2 AORes = vec2(1024.0, 768.0); +// const vec2 InvAORes = vec2(1.0/1024.0, 1.0/768.0); +// const vec2 NoiseScale = vec2(1024.0, 768.0) / 4.0; const float AOStrength = 1.9; -const float R = 0.3; -const float R2 = 0.3*0.3; -const float NegInvR2 = - 1.0 / (0.3*0.3); + +// const float R = 0.3; +// const float R2 = 0.3*0.3; +// const float NegInvR2 = - 1.0 / (0.3*0.3); + +const float R = 0.01; +const float R2 = 0.01*0.01; +const float NegInvR2 = - 1.0 / (0.01*0.01); + + + // can't use tan to initialize a const value -const float TanBias = 0.57735027; // tan(30.0 * PI / 180.0); +const float TanBias = 0.57735027; // tan(30.0 * PI / 180.0); const float MaxRadiusPixels = 50.0; const int NumDirections = 6; @@ -56,30 +80,43 @@ const int NumSamples = 4; out vec4 outFragColor; -float ViewSpaceZFromDepth(float d){ - // [0,1] -> [-1,1] clip space - d = d * 2.0 - 1.0; +// float ViewSpaceZFromDepth(float d){ +// // [0,1] -> [-1,1] clip space +// d = d * 2.0 - 1.0; - // Get view space Z - return -1.0 / (LinMAD.x * d + LinMAD.y); +// // Get view space Z +// return -1.0 / (LinMAD.x * d + LinMAD.y); +// } + +// vec3 UVToViewSpace(vec2 uv, float z){ +// //uv = UVToViewA * uv + UVToViewB; +// return vec3(uv * z, z); +// } + +// vec3 GetViewPos(vec2 uv){ +// float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); +// return UVToViewSpace(uv, z); +// } + +vec3 GetViewNormalFromTexture(vec2 uv) { + // convert [0,1] -> [-1,1], note: since we're normalizing + // we don't need to do v*2 - 1.0, we can just do a v-0.5 + return normalize(texture(normalTexture, uv).xyz - 0.5); +} + +float ViewSpaceZFromDepth(float d){ + return near / (d * depthScale - 1.0); } vec3 UVToViewSpace(vec2 uv, float z){ - //uv = UVToViewA * uv + UVToViewB; - return vec3(uv * z, z); + return vec3((depthTexCoordOffset + varTexcoord * depthTexCoordScale) * z, z); } vec3 GetViewPos(vec2 uv){ - float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); - return UVToViewSpace(uv, z); + float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); + return UVToViewSpace(uv, z); } -vec3 GetViewPosPoint(ivec2 uv){ - vec2 coord = vec2(gl_FragCoord.xy) + uv; - //float z = texelFetch(texture0, coord, 0).r; - float z = texture(depthTexture, uv).r; - return UVToViewSpace(uv, z); -} float TanToSin(float x){ return x * inversesqrt(x*x + 1.0); @@ -112,7 +149,8 @@ vec3 MinDiff(vec3 P, vec3 Pr, vec3 Pl){ } vec2 SnapUVOffset(vec2 uv){ - return round(uv * AORes) * InvAORes; + // return round(uv * AORes) * InvAORes; + return round(uv * renderTargetRes) * renderTargetResInv; } float Falloff(float d2){ @@ -180,7 +218,8 @@ void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPi } // Step size in uv space - stepSizeUv = stepSizePix * InvAORes; + // stepSizeUv = stepSizePix * InvAORes; + stepSizeUv = stepSizePix * renderTargetResInv; } float getRandom(vec2 uv){ @@ -188,27 +227,39 @@ float getRandom(vec2 uv){ } void main(void){ + mat4 projMatrix = getTransformCamera()._projection; + float numDirections = NumDirections; vec3 P, Pr, Pl, Pt, Pb; P = GetViewPos(varTexcoord); // Sample neighboring pixels - Pr = GetViewPos(varTexcoord + vec2( InvAORes.x, 0)); - Pl = GetViewPos(varTexcoord + vec2(-InvAORes.x, 0)); - Pt = GetViewPos(varTexcoord + vec2( 0, InvAORes.y)); - Pb = GetViewPos(varTexcoord + vec2( 0,-InvAORes.y)); + // Pr = GetViewPos(varTexcoord + vec2( InvAORes.x, 0)); + // Pl = GetViewPos(varTexcoord + vec2(-InvAORes.x, 0)); + // Pt = GetViewPos(varTexcoord + vec2( 0, InvAORes.y)); + // Pb = GetViewPos(varTexcoord + vec2( 0,-InvAORes.y)); + Pr = GetViewPos(varTexcoord + vec2( renderTargetResInv.x, 0)); + Pl = GetViewPos(varTexcoord + vec2(-renderTargetResInv.x, 0)); + Pt = GetViewPos(varTexcoord + vec2( 0, renderTargetResInv.y)); + Pb = GetViewPos(varTexcoord + vec2( 0,-renderTargetResInv.y)); // Calculate tangent basis vectors using the minimum difference vec3 dPdu = MinDiff(P, Pr, Pl); - vec3 dPdv = MinDiff(P, Pt, Pb) * (AORes.y * InvAORes.x); + // vec3 dPdv = MinDiff(P, Pt, Pb) * (AORes.y * InvAORes.x); + vec3 dPdv = MinDiff(P, Pt, Pb) * (renderTargetRes.y * renderTargetResInv.x); // Get the random samples from the noise function vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); // Calculate the projected size of the hemisphere - vec2 rayRadiusUV = 0.5 * R * FocalLen / -P.z; - float rayRadiusPix = rayRadiusUV.x * AORes.x; + // vec2 rayRadiusUV = 0.5 * R * FocalLen / -P.z; + // float rayRadiusPix = rayRadiusUV.x * AORes.x; + + // project the radius of the hemisphere into screen space + float w = P.z * projMatrix[2][3] + projMatrix[3][3]; + vec2 rayRadiusUV = (0.5 * R * vec2(projMatrix[0][0], projMatrix[1][1]) / w); // [-1,1] -> [0,1] uv + float rayRadiusPix = rayRadiusUV.x * renderTargetRes.x; float ao = 1.0; @@ -244,5 +295,6 @@ void main(void){ ao = 1.0 - ao / numDirections * AOStrength; } + outFragColor = vec4(vec3(ao), 1.0); -} \ No newline at end of file +} From 81ea8e88743bfd6f2ec6710da649dddf8018508a Mon Sep 17 00:00:00 2001 From: Christopher Root Date: Sat, 15 Aug 2015 14:27:08 -0700 Subject: [PATCH 036/170] Updating some comments and removing unnecessary code --- .../src/AmbientOcclusionEffect.cpp | 16 ++++--- .../render-utils/src/ambient_occlusion.slf | 47 +++++++------------ 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index b94bd09538..962b57a22c 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -219,22 +219,26 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons batch._glUniform1f(_gSampleRadiusLoc, g_sample_rad); batch._glUniform1f(_gIntensityLoc, g_intensity); - // setup uniforms for extracting depth from the depth buffer and - // converting that depth to a camera-space position, same as DeferredLightingEffect.cpp + // setup uniforms for unpacking a view-space position from the depth buffer + // This is code taken from DeferredLightEffect.render() method in DeferredLightingEffect.cpp. + // DeferredBuffer.slh shows how the unpacking is done and what variables are needed. + + // initialize the view-space unpacking uniforms using frustum data float left, right, bottom, top, nearVal, farVal; glm::vec4 nearClipPlane, farClipPlane; + args->_viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - batch._glUniform1f(_nearLoc, nearVal); - float depthScale = (farVal - nearVal) / farVal; - batch._glUniform1f(_depthScaleLoc, depthScale); - float nearScale = -1.0f / nearVal; float depthTexCoordScaleS = (right - left) * nearScale / sWidth; float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; + + // now set the position-unpacking unforms + batch._glUniform1f(_nearLoc, nearVal); + batch._glUniform1f(_depthScaleLoc, depthScale); batch._glUniform2f(_depthTexCoordOffsetLoc, depthTexCoordOffsetS, depthTexCoordOffsetT); batch._glUniform2f(_depthTexCoordScaleLoc, depthTexCoordScaleS, depthTexCoordScaleT); diff --git a/libraries/render-utils/src/ambient_occlusion.slf b/libraries/render-utils/src/ambient_occlusion.slf index 73bed26a9c..44ff79a179 100644 --- a/libraries/render-utils/src/ambient_occlusion.slf +++ b/libraries/render-utils/src/ambient_occlusion.slf @@ -52,19 +52,8 @@ uniform vec2 renderTargetResInv; const float PI = 3.14159265; -// const vec2 FocalLen = vec2(1.0, 1.0); -// const vec2 LinMAD = vec2(0.1-10.0, 0.1+10.0) / (2.0*0.1*10.0); - -// const vec2 AORes = vec2(1024.0, 768.0); -// const vec2 InvAORes = vec2(1.0/1024.0, 1.0/768.0); -// const vec2 NoiseScale = vec2(1024.0, 768.0) / 4.0; - const float AOStrength = 1.9; -// const float R = 0.3; -// const float R2 = 0.3*0.3; -// const float NegInvR2 = - 1.0 / (0.3*0.3); - const float R = 0.01; const float R2 = 0.01*0.01; const float NegInvR2 = - 1.0 / (0.01*0.01); @@ -80,38 +69,38 @@ const int NumSamples = 4; out vec4 outFragColor; -// float ViewSpaceZFromDepth(float d){ -// // [0,1] -> [-1,1] clip space -// d = d * 2.0 - 1.0; - -// // Get view space Z -// return -1.0 / (LinMAD.x * d + LinMAD.y); -// } - -// vec3 UVToViewSpace(vec2 uv, float z){ -// //uv = UVToViewA * uv + UVToViewB; -// return vec3(uv * z, z); -// } - -// vec3 GetViewPos(vec2 uv){ -// float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); -// return UVToViewSpace(uv, z); -// } - +/** + * Gets the normal in view space from a normal texture. + * uv: the uv texture coordinates to look up in the texture at. + */ vec3 GetViewNormalFromTexture(vec2 uv) { // convert [0,1] -> [-1,1], note: since we're normalizing // we don't need to do v*2 - 1.0, we can just do a v-0.5 return normalize(texture(normalTexture, uv).xyz - 0.5); } +/** + * Gets the linearized depth in view space. + * d: the depth value [0-1], usually from a depth texture to convert. + */ float ViewSpaceZFromDepth(float d){ return near / (d * depthScale - 1.0); } +/** + * Converts a uv coordinate and depth value into a 3D view space coordinate. + * uv: the uv coordinates to convert + * z: the view space depth of the uv coordinate. + */ vec3 UVToViewSpace(vec2 uv, float z){ return vec3((depthTexCoordOffset + varTexcoord * depthTexCoordScale) * z, z); } +/** + * Converts a uv coordinate into a 3D view space coordinate. + * The depth of the uv coord is determined from the depth texture. + * uv: the uv coordinates to convert + */ vec3 GetViewPos(vec2 uv){ float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); return UVToViewSpace(uv, z); From 8ea6048ec75d8c21c3f6d32090ad5fda8f736432 Mon Sep 17 00:00:00 2001 From: Christopher Root Date: Sat, 15 Aug 2015 14:31:03 -0700 Subject: [PATCH 037/170] Removing commented out code --- libraries/render-utils/src/ambient_occlusion.slf | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/libraries/render-utils/src/ambient_occlusion.slf b/libraries/render-utils/src/ambient_occlusion.slf index 44ff79a179..f304644604 100644 --- a/libraries/render-utils/src/ambient_occlusion.slf +++ b/libraries/render-utils/src/ambient_occlusion.slf @@ -54,6 +54,8 @@ const float PI = 3.14159265; const float AOStrength = 1.9; + +// TODO: R (radius) should be exposed as a uniform parameter const float R = 0.01; const float R2 = 0.01*0.01; const float NegInvR2 = - 1.0 / (0.01*0.01); @@ -224,10 +226,6 @@ void main(void){ P = GetViewPos(varTexcoord); // Sample neighboring pixels - // Pr = GetViewPos(varTexcoord + vec2( InvAORes.x, 0)); - // Pl = GetViewPos(varTexcoord + vec2(-InvAORes.x, 0)); - // Pt = GetViewPos(varTexcoord + vec2( 0, InvAORes.y)); - // Pb = GetViewPos(varTexcoord + vec2( 0,-InvAORes.y)); Pr = GetViewPos(varTexcoord + vec2( renderTargetResInv.x, 0)); Pl = GetViewPos(varTexcoord + vec2(-renderTargetResInv.x, 0)); Pt = GetViewPos(varTexcoord + vec2( 0, renderTargetResInv.y)); @@ -235,17 +233,12 @@ void main(void){ // Calculate tangent basis vectors using the minimum difference vec3 dPdu = MinDiff(P, Pr, Pl); - // vec3 dPdv = MinDiff(P, Pt, Pb) * (AORes.y * InvAORes.x); vec3 dPdv = MinDiff(P, Pt, Pb) * (renderTargetRes.y * renderTargetResInv.x); // Get the random samples from the noise function vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); // Calculate the projected size of the hemisphere - // vec2 rayRadiusUV = 0.5 * R * FocalLen / -P.z; - // float rayRadiusPix = rayRadiusUV.x * AORes.x; - - // project the radius of the hemisphere into screen space float w = P.z * projMatrix[2][3] + projMatrix[3][3]; vec2 rayRadiusUV = (0.5 * R * vec2(projMatrix[0][0], projMatrix[1][1]) / w); // [-1,1] -> [0,1] uv float rayRadiusPix = rayRadiusUV.x * renderTargetRes.x; From 21dc58eb85fcf795f98fa8e7be7d24ccc1274e10 Mon Sep 17 00:00:00 2001 From: Christopher Root Date: Sat, 15 Aug 2015 14:40:34 -0700 Subject: [PATCH 038/170] Improving readability in shader --- .../render-utils/src/ambient_occlusion.slf | 167 +++++++++--------- 1 file changed, 82 insertions(+), 85 deletions(-) diff --git a/libraries/render-utils/src/ambient_occlusion.slf b/libraries/render-utils/src/ambient_occlusion.slf index f304644604..8ab78891b0 100644 --- a/libraries/render-utils/src/ambient_occlusion.slf +++ b/libraries/render-utils/src/ambient_occlusion.slf @@ -103,95 +103,94 @@ vec3 UVToViewSpace(vec2 uv, float z){ * The depth of the uv coord is determined from the depth texture. * uv: the uv coordinates to convert */ -vec3 GetViewPos(vec2 uv){ +vec3 GetViewPos(vec2 uv) { float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); return UVToViewSpace(uv, z); } -float TanToSin(float x){ - return x * inversesqrt(x*x + 1.0); +float TanToSin(float x) { + return x * inversesqrt(x*x + 1.0); } -float InvLength(vec2 V){ - return inversesqrt(dot(V,V)); +float InvLength(vec2 V) { + return inversesqrt(dot(V, V)); } -float Tangent(vec3 V){ - return V.z * InvLength(V.xy); +float Tangent(vec3 V) { + return V.z * InvLength(V.xy); } -float BiasedTangent(vec3 V){ - return V.z * InvLength(V.xy) + TanBias; +float BiasedTangent(vec3 V) { + return V.z * InvLength(V.xy) + TanBias; } -float Tangent(vec3 P, vec3 S){ +float Tangent(vec3 P, vec3 S) { return -(P.z - S.z) * InvLength(S.xy - P.xy); } -float Length2(vec3 V){ - return dot(V,V); +float Length2(vec3 V) { + return dot(V, V); } -vec3 MinDiff(vec3 P, vec3 Pr, vec3 Pl){ +vec3 MinDiff(vec3 P, vec3 Pr, vec3 Pl) { vec3 V1 = Pr - P; vec3 V2 = P - Pl; return (Length2(V1) < Length2(V2)) ? V1 : V2; } -vec2 SnapUVOffset(vec2 uv){ - // return round(uv * AORes) * InvAORes; +vec2 SnapUVOffset(vec2 uv) { return round(uv * renderTargetRes) * renderTargetResInv; } -float Falloff(float d2){ - return d2 * NegInvR2 + 1.0f; +float Falloff(float d2) { + return d2 * NegInvR2 + 1.0f; } -float HorizonOcclusion( vec2 deltaUV, vec3 P, vec3 dPdu, vec3 dPdv, float randstep, float numSamples){ - float ao = 0; +float HorizonOcclusion(vec2 deltaUV, vec3 P, vec3 dPdu, vec3 dPdv, float randstep, float numSamples) { + float ao = 0; - // Offset the first coord with some noise - vec2 uv = varTexcoord + SnapUVOffset(randstep*deltaUV); - deltaUV = SnapUVOffset( deltaUV ); + // Offset the first coord with some noise + vec2 uv = varTexcoord + SnapUVOffset(randstep*deltaUV); + deltaUV = SnapUVOffset(deltaUV); - // Calculate the tangent vector - vec3 T = deltaUV.x * dPdu + deltaUV.y * dPdv; + // Calculate the tangent vector + vec3 T = deltaUV.x * dPdu + deltaUV.y * dPdv; - // Get the angle of the tangent vector from the viewspace axis - float tanH = BiasedTangent(T); - float sinH = TanToSin(tanH); + // Get the angle of the tangent vector from the viewspace axis + float tanH = BiasedTangent(T); + float sinH = TanToSin(tanH); - float tanS; - float d2; - vec3 S; + float tanS; + float d2; + vec3 S; - // Sample to find the maximum angle - for(float s = 1; s <= numSamples; ++s){ - uv += deltaUV; - S = GetViewPos(uv); - tanS = Tangent(P, S); - d2 = Length2(S - P); + // Sample to find the maximum angle + for (float s = 1; s <= numSamples; ++s) { + uv += deltaUV; + S = GetViewPos(uv); + tanS = Tangent(P, S); + d2 = Length2(S - P); - // Is the sample within the radius and the angle greater? - if(d2 < R2 && tanS > tanH) - { - float sinS = TanToSin(tanS); - // Apply falloff based on the distance - ao += Falloff(d2) * (sinS - sinH); + // Is the sample within the radius and the angle greater? + if (d2 < R2 && tanS > tanH) { + float sinS = TanToSin(tanS); + // Apply falloff based on the distance + ao += Falloff(d2) * (sinS - sinH); - tanH = tanS; - sinH = sinS; - } - } - return ao; + tanH = tanS; + sinH = sinS; + } + } + return ao; } -vec2 RotateDirections(vec2 Dir, vec2 CosSin){ - return vec2(Dir.x*CosSin.x - Dir.y*CosSin.y, Dir.x*CosSin.y + Dir.y*CosSin.x); +vec2 RotateDirections(vec2 Dir, vec2 CosSin) { + return vec2(Dir.x*CosSin.x - Dir.y*CosSin.y, + Dir.x*CosSin.y + Dir.y*CosSin.x); } -void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPix, float rand){ +void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPix, float rand) { // Avoid oversampling if numSteps is greater than the kernel radius in pixels numSteps = min(NumSamples, rayRadiusPix); @@ -200,8 +199,7 @@ void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPi // Clamp numSteps if it is greater than the max kernel footprint float maxNumSteps = MaxRadiusPixels / stepSizePix; - if (maxNumSteps < numSteps) - { + if (maxNumSteps < numSteps) { // Use dithering to avoid AO discontinuities numSteps = floor(maxNumSteps + rand); numSteps = max(numSteps, 1); @@ -209,23 +207,22 @@ void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPi } // Step size in uv space - // stepSizeUv = stepSizePix * InvAORes; stepSizeUv = stepSizePix * renderTargetResInv; } -float getRandom(vec2 uv){ +float getRandom(vec2 uv) { return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453); } -void main(void){ +void main(void) { mat4 projMatrix = getTransformCamera()._projection; - float numDirections = NumDirections; + float numDirections = NumDirections; - vec3 P, Pr, Pl, Pt, Pb; - P = GetViewPos(varTexcoord); + vec3 P, Pr, Pl, Pt, Pb; + P = GetViewPos(varTexcoord); - // Sample neighboring pixels + // Sample neighboring pixels Pr = GetViewPos(varTexcoord + vec2( renderTargetResInv.x, 0)); Pl = GetViewPos(varTexcoord + vec2(-renderTargetResInv.x, 0)); Pt = GetViewPos(varTexcoord + vec2( 0, renderTargetResInv.y)); @@ -236,9 +233,9 @@ void main(void){ vec3 dPdv = MinDiff(P, Pt, Pb) * (renderTargetRes.y * renderTargetResInv.x); // Get the random samples from the noise function - vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); + vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); - // Calculate the projected size of the hemisphere + // Calculate the projected size of the hemisphere float w = P.z * projMatrix[2][3] + projMatrix[3][3]; vec2 rayRadiusUV = (0.5 * R * vec2(projMatrix[0][0], projMatrix[1][1]) / w); // [-1,1] -> [0,1] uv float rayRadiusPix = rayRadiusUV.x * renderTargetRes.x; @@ -246,36 +243,36 @@ void main(void){ float ao = 1.0; // Make sure the radius of the evaluated hemisphere is more than a pixel - if(rayRadiusPix > 1.0){ - ao = 0.0; - float numSteps; - vec2 stepSizeUV; + if(rayRadiusPix > 1.0) { + ao = 0.0; + float numSteps; + vec2 stepSizeUV; - // Compute the number of steps - ComputeSteps(stepSizeUV, numSteps, rayRadiusPix, random.z); + // Compute the number of steps + ComputeSteps(stepSizeUV, numSteps, rayRadiusPix, random.z); - float alpha = 2.0 * PI / numDirections; + float alpha = 2.0 * PI / numDirections; - // Calculate the horizon occlusion of each direction - for(float d = 0; d < numDirections; ++d){ - float theta = alpha * d; + // Calculate the horizon occlusion of each direction + for(float d = 0; d < numDirections; ++d) { + float theta = alpha * d; - // Apply noise to the direction - vec2 dir = RotateDirections(vec2(cos(theta), sin(theta)), random.xy); - vec2 deltaUV = dir * stepSizeUV; + // Apply noise to the direction + vec2 dir = RotateDirections(vec2(cos(theta), sin(theta)), random.xy); + vec2 deltaUV = dir * stepSizeUV; - // Sample the pixels along the direction - ao += HorizonOcclusion( deltaUV, - P, - dPdu, - dPdv, - random.z, - numSteps); - } + // Sample the pixels along the direction + ao += HorizonOcclusion( deltaUV, + P, + dPdu, + dPdv, + random.z, + numSteps); + } - // Average the results and produce the final AO - ao = 1.0 - ao / numDirections * AOStrength; - } + // Average the results and produce the final AO + ao = 1.0 - ao / numDirections * AOStrength; + } outFragColor = vec4(vec3(ao), 1.0); From 5ecdbaa19d591cbd4f51f6914cfe696c63a58161 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 15 Aug 2015 15:18:25 -0700 Subject: [PATCH 039/170] have functions that change polyvox voxes return true if they changed anything, else false. added voxelCoordsToWorldCoords and worldCoordsToVoxelCoords --- examples/voxels.js | 16 +++- .../src/RenderablePolyVoxEntityItem.cpp | 59 +++++++++------ .../src/RenderablePolyVoxEntityItem.h | 18 ++--- .../entities/src/EntityScriptingInterface.cpp | 75 ++++++++++++++----- .../entities/src/EntityScriptingInterface.h | 5 +- libraries/entities/src/PolyVoxEntityItem.h | 15 ++-- 6 files changed, 128 insertions(+), 60 deletions(-) diff --git a/examples/voxels.js b/examples/voxels.js index 9627b40701..c70111153b 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -7,10 +7,15 @@ function attemptVoxelChange(intersection) { var success = false; for (var i = 0; i < ids.length; i++) { var id = ids[i]; + // var voxelCoords = Entities.worldCoordsToVoxelCoords(id, intersection.intersection); + if (controlHeld) { // hold control to erase a sphere - if (Entities.setVoxelSphere(id, intersection.intersection, 1.0, 0)) { - success = true; + for (var r = 1.0; r < 17.0; r++) { + if (Entities.setVoxelSphere(id, intersection.intersection, r, 0)) { + success = true; + break; + } } } else if (shiftHeld) { // hold shift to set all voxels to 255 @@ -19,8 +24,11 @@ function attemptVoxelChange(intersection) { } } else { // no modifier key means to add a sphere - if (Entities.setVoxelSphere(id, intersection.intersection, 1.0, 255)) { - success = true; + for (var r = 1.0; r < 17.0; r++) { + if (Entities.setVoxelSphere(id, intersection.intersection, r, 255)) { + success = true; + break; + } } } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index b803e32e1e..5b0c4ee691 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -230,78 +230,88 @@ uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { return _volData->getVoxelAt(x, y, z); } -void RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { +bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { // set a voxel without recompressing the voxel data assert(_volData); + bool result = false; if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return; + return false; } - updateOnCount(x, y, z, toValue); + result = updateOnCount(x, y, z, toValue); if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); } else { _volData->setVoxelAt(x, y, z, toValue); } + + return result; } -void RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { +bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { - return; + return false; } - setVoxelInternal(x, y, z, toValue); + bool result = setVoxelInternal(x, y, z, toValue); compressVolumeData(); + return result; } -void RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) { +bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) { // keep _onCount up to date if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return; + return false; } uint8_t uVoxelValue = getVoxel(x, y, z); if (toValue != 0) { if (uVoxelValue == 0) { _onCount++; + return true; } } else { // toValue == 0 if (uVoxelValue != 0) { _onCount--; assert(_onCount >= 0); + return true; } } + return false; } -void RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { +bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { + bool result = false; if (_locked) { - return; + return false; } for (int z = 0; z < _voxelVolumeSize.z; z++) { for (int y = 0; y < _voxelVolumeSize.y; y++) { for (int x = 0; x < _voxelVolumeSize.x; x++) { - setVoxelInternal(x, y, z, toValue); + result |= setVoxelInternal(x, y, z, toValue); } } } compressVolumeData(); + return result; } -void RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) { +bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) { if (_locked) { - return; + return false; } // same as setVoxel but takes a vector rather than 3 floats. - setVoxel((int)position.x, (int)position.y, (int)position.z, toValue); + return setVoxel((int)position.x, (int)position.y, (int)position.z, toValue); } -void RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { +bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { + bool result = false; if (_locked) { - return; + return false; } // This three-level for loop iterates over every voxel in the volume @@ -314,22 +324,22 @@ void RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi float fDistToCenter = glm::distance(pos, center); // If the current voxel is less than 'radius' units from the center then we make it solid. if (fDistToCenter <= radius) { - updateOnCount(x, y, z, toValue); - setVoxelInternal(x, y, z, toValue); + result |= setVoxelInternal(x, y, z, toValue); } } } } compressVolumeData(); + return result; } -void RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) { +bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) { // glm::vec3 centerVoxelCoords = worldToVoxelCoordinates(centerWorldCoords); glm::vec4 centerVoxelCoords = worldToVoxelMatrix() * glm::vec4(centerWorldCoords, 1.0f); glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units float scaleY = scale.y; float radiusVoxelCoords = radiusWorldCoords / scaleY; - setSphereInVolume(glm::vec3(centerVoxelCoords), radiusVoxelCoords, toValue); + return setSphereInVolume(glm::vec3(centerVoxelCoords), radiusVoxelCoords, toValue); } class RaycastFunctor @@ -508,7 +518,6 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() { for (int y = 0; y < voxelYSize; y++) { for (int x = 0; x < voxelXSize; x++) { int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; - updateOnCount(x, y, z, uncompressedData[uncompressedIndex]); setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); } } @@ -845,3 +854,11 @@ namespace render { } } } + +glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3 voxelCoords) { + return glm::vec3(voxelToWorldMatrix() * glm::vec4(voxelCoords, 1.0f)); +} + +glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3 worldCoords) { + return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); +} diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index d495900ce9..17c8934577 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -54,9 +54,9 @@ public: } virtual uint8_t getVoxel(int x, int y, int z); - virtual void setVoxel(int x, int y, int z, uint8_t toValue); + virtual bool setVoxel(int x, int y, int z, uint8_t toValue); - void updateOnCount(int x, int y, int z, uint8_t new_value); + bool updateOnCount(int x, int y, int z, uint8_t new_value); void render(RenderArgs* args); virtual bool supportsDetailedRayIntersection() const { return true; } @@ -78,16 +78,16 @@ public: virtual bool isReadyToComputeShape(); virtual void computeShapeInfo(ShapeInfo& info); + virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3 voxelCoords); + virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3 worldCoords); // coords are in voxel-volume space - virtual void setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue); + virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue); + virtual bool setVoxelInVolume(glm::vec3 position, uint8_t toValue); // coords are in world-space - virtual void setSphere(glm::vec3 center, float radius, uint8_t toValue); - - virtual void setAll(uint8_t toValue); - - virtual void setVoxelInVolume(glm::vec3 position, uint8_t toValue); + virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue); + virtual bool setAll(uint8_t toValue); virtual void setXTextureURL(QString xTextureURL); virtual void setYTextureURL(QString yTextureURL); @@ -107,7 +107,7 @@ private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. - void setVoxelInternal(int x, int y, int z, uint8_t toValue); + bool setVoxelInternal(int x, int y, int z, uint8_t toValue); void compressVolumeData(); void decompressVolumeData(); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 9654de0fc4..c3e71cef82 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -415,7 +415,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra } bool EntityScriptingInterface::setVoxels(QUuid entityID, - std::function actor) { + std::function actor) { if (!_entityTree) { return false; } @@ -435,7 +435,7 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID, auto polyVoxEntity = std::dynamic_pointer_cast(entity); _entityTree->lockForWrite(); - actor(*polyVoxEntity); + bool result = actor(*polyVoxEntity); entity->setLastEdited(now); entity->setLastBroadcast(now); _entityTree->unlock(); @@ -448,42 +448,41 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID, properties.setLastEdited(now); queueEntityMessage(PacketType::EntityEdit, entityID, properties); - return true; + return result; } bool EntityScriptingInterface::setPoints(QUuid entityID, std::function actor) { if (!_entityTree) { return false; } - + EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; } - + EntityTypes::EntityType entityType = entity->getType(); - + if (entityType != EntityTypes::Line) { return false; } - + auto now = usecTimestampNow(); - + auto lineEntity = std::static_pointer_cast(entity); _entityTree->lockForWrite(); bool success = actor(*lineEntity); entity->setLastEdited(now); entity->setLastBroadcast(now); _entityTree->unlock(); - + _entityTree->lockForRead(); EntityItemProperties properties = entity->getProperties(); _entityTree->unlock(); - + properties.setLinePointsDirty(); properties.setLastEdited(now); - - + queueEntityMessage(PacketType::EntityEdit, entityID, properties); return success; } @@ -491,19 +490,19 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::functiongetType(); - + if (entityType == EntityTypes::Line) { return setPoints(entityID, [points](LineEntityItem& lineEntity) -> bool { return (LineEntityItem*)lineEntity.setLinePoints(points); }); } - + return false; } @@ -658,3 +657,43 @@ QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID, }); return result; } + +glm::vec3 EntityScriptingInterface::voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords) { + if (!_entityTree) { + return glm::vec3(0.0f); + } + + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToWorldCoords no entity with ID" << entityID; + return glm::vec3(0.0f); + } + + EntityTypes::EntityType entityType = entity->getType(); + if (entityType != EntityTypes::PolyVox) { + return glm::vec3(0.0f); + } + + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->voxelCoordsToWorldCoords(voxelCoords); +} + +glm::vec3 EntityScriptingInterface::worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords) { + if (!_entityTree) { + return glm::vec3(0.0f); + } + + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + qCDebug(entities) << "EntityScriptingInterface::worldCoordsToVoxelCoords no entity with ID" << entityID; + return glm::vec3(0.0f); + } + + EntityTypes::EntityType entityType = entity->getType(); + if (entityType != EntityTypes::PolyVox) { + return glm::vec3(0.0f); + } + + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->worldCoordsToVoxelCoords(worldCoords); +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 434134d2cd..e5bff8e80a 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -134,6 +134,9 @@ public slots: Q_INVOKABLE QVector getActionIDs(const QUuid& entityID); Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID); + Q_INVOKABLE glm::vec3 voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords); + Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); + signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -162,7 +165,7 @@ signals: private: bool actionWorker(const QUuid& entityID, std::function actor); - bool setVoxels(QUuid entityID, std::function actor); + bool setVoxels(QUuid entityID, std::function actor); bool setPoints(QUuid entityID, std::function actor); void queueEntityMessage(PacketType::Value packetType, EntityItemID entityID, const EntityItemProperties& properties); diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 29d0b2e595..563624a1c7 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -74,17 +74,18 @@ class PolyVoxEntityItem : public EntityItem { static const PolyVoxSurfaceStyle DEFAULT_VOXEL_SURFACE_STYLE; // coords are in voxel-volume space - virtual void setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) {} + virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { return false; } + virtual bool setVoxelInVolume(glm::vec3 position, uint8_t toValue) { return false; } + + virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3 voxelCoords) { return glm::vec3(0.0f); } + virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3 worldCoords) { return glm::vec3(0.0f); } // coords are in world-space - virtual void setSphere(glm::vec3 center, float radius, uint8_t toValue) {} - - virtual void setAll(uint8_t toValue) {} - - virtual void setVoxelInVolume(glm::vec3 position, uint8_t toValue) {} + virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) { return false; } + virtual bool setAll(uint8_t toValue) { return false; } virtual uint8_t getVoxel(int x, int y, int z) { return 0; } - virtual void setVoxel(int x, int y, int z, uint8_t toValue) {} + virtual bool setVoxel(int x, int y, int z, uint8_t toValue) { return false; } static QByteArray makeEmptyVoxelData(quint16 voxelXSize = 16, quint16 voxelYSize = 16, quint16 voxelZSize = 16); From c6530c4cecbc5e9f552570869af6a942e3149396 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 15 Aug 2015 16:16:27 -0700 Subject: [PATCH 040/170] fix newly created polyvox collision-hull bug --- .../src/RenderablePolyVoxEntityItem.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5b0c4ee691..9f97585fab 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -158,7 +158,10 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) void RenderablePolyVoxEntityItem::updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { // if we are switching to or from "edged" we need to force a resize of _volData. - if (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { + bool wasEdged = (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES); + bool willBeEdged = (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES); + + if (wasEdged != willBeEdged) { if (_volData) { delete _volData; } @@ -611,6 +614,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { } } else { unsigned int i = 0; + for (int z = 0; z < _voxelVolumeSize.z; z++) { for (int y = 0; y < _voxelVolumeSize.y; y++) { for (int x = 0; x < _voxelVolumeSize.x; x++) { @@ -619,6 +623,11 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { float offL = -0.5f; float offH = 0.5f; + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { + offL += 1.0f; + offH += 1.0f; + } + glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); From 558f68c150af1ba21ebad03937dbdb5d894d9b91 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 15 Aug 2015 16:53:47 -0700 Subject: [PATCH 041/170] increase max polyvox dimension from 32 to 128. don't recompress compressed data unless something changed../../libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp --- libraries/entities/src/PolyVoxEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index c9f3705712..30dded3714 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -22,7 +22,7 @@ const glm::vec3 PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE = glm::vec3(32, 32, 32); -const float PolyVoxEntityItem::MAX_VOXEL_DIMENSION = 32.0f; +const float PolyVoxEntityItem::MAX_VOXEL_DIMENSION = 128.0f; const QByteArray PolyVoxEntityItem::DEFAULT_VOXEL_DATA(PolyVoxEntityItem::makeEmptyVoxelData()); const PolyVoxEntityItem::PolyVoxSurfaceStyle PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE = PolyVoxEntityItem::SURFACE_MARCHING_CUBES; From f6cbeb37f866d46a5cd44735b27e42c50fd5eabb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 16 Aug 2015 09:35:50 -0700 Subject: [PATCH 042/170] put a green sphere at the point being pointed to --- examples/pointer.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index 2791e06466..99c2bcb23b 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -12,6 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html var lineEntityID = null; +var sphereEntityID = null; var lineIsRezzed = false; var BUTTON_SIZE = 32; @@ -56,16 +57,24 @@ function nearLinePoint(targetPosition) { function removeLine() { - if (lineIsRezzed) { - Entities.deleteEntity(lineEntityID); - lineEntityID = null; - lineIsRezzed = false; - } + if (lineIsRezzed) { + Entities.deleteEntity(lineEntityID); + if (sphereEntityID) { + Entities.deleteEntity(sphereEntityID); + } + lineEntityID = null; + sphereEntityID = null; + lineIsRezzed = false; + } } function createOrUpdateLine(event) { var pickRay = Camera.computePickRay(event.x, event.y); + if (sphereEntityID) { + Entities.deleteEntity(sphereEntityID); + sphereEntityID = null; + } var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking var props = Entities.getEntityProperties(intersection.entityID); @@ -78,7 +87,6 @@ function createOrUpdateLine(event) { position: MyAvatar.position, lifetime: 15 + props.lifespan // renew lifetime }); - // Entities.setAllPoints(lineEntityID, points); } else { lineIsRezzed = true; lineEntityID = Entities.addEntity({ @@ -90,6 +98,15 @@ function createOrUpdateLine(event) { lifetime: 15 // if someone crashes while pointing, don't leave the line there forever. }); } + + sphereEntityID = Entities.addEntity({ + type: "Sphere", + position: intersection.intersection, + ignoreForCollisions: 1, + dimensions: { x: 0.6, y: 0.6, z: 0.6 }, + color: { red: 0, green: 255, blue: 0 }, + lifetime: 15 // if someone crashes while pointing, don't leave the line there forever. + }); } else { removeLine(); } From 6db115bed59f5b1f0577ec39ba09461bcc7bd887 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 16 Aug 2015 09:36:13 -0700 Subject: [PATCH 043/170] try to make adding and removing voxels work more like expected --- examples/voxels.js | 120 ++++++++++++++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 35 deletions(-) diff --git a/examples/voxels.js b/examples/voxels.js index c70111153b..0ef9fe9fab 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -1,38 +1,81 @@ var controlHeld = false; var shiftHeld = false; +function floorVector(v) { + return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)}; +} -function attemptVoxelChange(intersection) { - var ids = Entities.findEntities(intersection.intersection, 10); - var success = false; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - // var voxelCoords = Entities.worldCoordsToVoxelCoords(id, intersection.intersection); - - if (controlHeld) { - // hold control to erase a sphere - for (var r = 1.0; r < 17.0; r++) { - if (Entities.setVoxelSphere(id, intersection.intersection, r, 0)) { - success = true; - break; - } - } - } else if (shiftHeld) { - // hold shift to set all voxels to 255 - if (Entities.setAllVoxels(id, 255)) { - success = true; - } - } else { - // no modifier key means to add a sphere - for (var r = 1.0; r < 17.0; r++) { - if (Entities.setVoxelSphere(id, intersection.intersection, r, 255)) { - success = true; - break; - } - } - } +function backUpByOneVoxel(pickRayDirInVoxelSpace, originalVoxelPosition, low, high) { + // try to back up along pickRay an amount that changes only one coordinate. this does + // a binary search for a pickRay multiplier that only causes one of the 3 coordinates to change. + var originalVoxelCoords = floorVector(originalVoxelPosition) + if (high - low < 0.001) { + print("voxel backup-by-1 failed"); + // give up. + return oCoords; } - return success; + var middle = (low + high) / 2.0; + + var backupVector = Vec3.multiply(pickRayDirInVoxelSpace, -middle); + print("backupVector = " + "{" + backupVector.x + ", " + backupVector.y + ", " + backupVector.z + "}"); + var nPosition = Vec3.sum(originalVoxelPosition, backupVector); // possible neighbor coordinates + var nCoords = floorVector(nPosition); + + print("middle = " + middle + + " -- {" + originalVoxelCoords.x + ", " + originalVoxelCoords.y + ", " + originalVoxelCoords.z + "}" + + " -- {" + nCoords.x + ", " + nCoords.y + ", " + nCoords.z + "}"); + + + if (nCoords.x == originalVoxelCoords.x && + nCoords.y == originalVoxelCoords.y && + nCoords.z == originalVoxelCoords.z) { + // still in the original voxel. back up more... + return backUpByOneVoxel(pickRayDirInVoxelSpace, originalVoxelPosition, middle, high); + } + + if (nCoords.x != originalVoxelCoords.x && + nCoords.y == originalVoxelCoords.y && + nCoords.z == originalVoxelCoords.z) { + return nCoords; + } + if (nCoords.x == originalVoxelCoords.x && + nCoords.y != originalVoxelCoords.y && + nCoords.z == originalVoxelCoords.z) { + return nCoords; + } + if (nCoords.x == originalVoxelCoords.x && + nCoords.y == originalVoxelCoords.y && + nCoords.z != originalVoxelCoords.z) { + return nCoords; + } + + // more than one coordinate changed, but no less... + return backUpByOneVoxel(pickRayDirInVoxelSpace, originalVoxelPosition, low, middle); +} + + +function attemptVoxelChange(pickRayDir, intersection) { + var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection); + var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir); + + print("voxelPosition = " + voxelPosition.x + ", " + voxelPosition.y + ", " + voxelPosition.z); + print("pickRay = " + pickRayDir.x + ", " + pickRayDir.y + ", " + pickRayDir.z); + print("pickRayInVoxelSpace = " + + pickRayDirInVoxelSpace.x + ", " + pickRayDirInVoxelSpace.y + ", " + pickRayDirInVoxelSpace.z); + + if (controlHeld) { + // hold control to erase a voxel + return Entities.setVoxel(intersection.entityID, floorVector(voxelPosition), 0); + } else if (shiftHeld) { + // hold shift to set all voxels to 255 + return Entities.setAllVoxels(intersection.entityID, 255); + } else { + // no modifier key to add a voxel + var backOneVoxel = backUpByOneVoxel(pickRayDirInVoxelSpace, voxelPosition, 0.0, 1.0); + return Entities.setVoxel(intersection.entityID, backOneVoxel, 255); + } + + // Entities.setVoxelSphere(id, intersection.intersection, radius, 0) } @@ -43,20 +86,27 @@ function mousePressEvent(event) { var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking + var properties; - // we've used a picking ray to decide where to add the new sphere of voxels. If we pick nothing - // or if we pick a non-PolyVox entity, we fall through to the next picking attempt. if (intersection.intersects) { - if (attemptVoxelChange(intersection)) { - return; + properties = Entities.getEntityProperties(intersection.entityID); + print("got accurate pick"); + if (properties.type == "PolyVox") { + if (attemptVoxelChange(pickRay.direction, intersection)) { + return; + } + } else { + print("not a polyvox: " + properties.type); } } + print("trying bounding-box pick"); + // if the PolyVox entity is empty, we can't pick against its "on" voxels. try picking against its // bounding box, instead. intersection = Entities.findRayIntersection(pickRay, false); // bounding box picking if (intersection.intersects) { - attemptVoxelChange(intersection); + attemptVoxelChange(pickRay.direction, intersection); } } From b129c2a93342c0c86363ad1c6959406affe81c54 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 16 Aug 2015 09:36:54 -0700 Subject: [PATCH 044/170] in edged mode, make sure border voxels are zeroed out. expose some more voxel matrix math to javascript --- .../src/RenderablePolyVoxEntityItem.cpp | 84 +++++++++++++++---- .../src/RenderablePolyVoxEntityItem.h | 7 +- .../entities/src/EntityScriptingInterface.cpp | 40 +++++++++ .../entities/src/EntityScriptingInterface.h | 2 + libraries/entities/src/PolyVoxEntityItem.h | 2 + 5 files changed, 117 insertions(+), 18 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 9f97585fab..108660f386 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #if defined(__GNUC__) && !defined(__clang__) @@ -67,7 +68,7 @@ RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { } bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) { + int x, int y, int z) { // x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords. switch (surfaceStyle) { case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: @@ -206,6 +207,11 @@ glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { return scaled; } +glm::mat4 RenderablePolyVoxEntityItem::localToVoxelMatrix() const { + glm::mat4 localToModelMatrix = glm::inverse(voxelToLocalMatrix()); + return localToModelMatrix; +} + glm::mat4 RenderablePolyVoxEntityItem::voxelToWorldMatrix() const { glm::mat4 rotation = glm::mat4_cast(getRotation()); glm::mat4 translation = glm::translate(getPosition()); @@ -252,13 +258,32 @@ bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t return result; } +void RenderablePolyVoxEntityItem::clearEdges() { + // if we are in an edged mode, make sure the outside surfaces are zeroed out. + if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { + for (int z = 0; z < _volData->getDepth(); z++) { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int x = 0; x < _volData->getWidth(); x++) { + if (x == 0 || y == 0 || z == 0 || + x == _volData->getWidth() - 1 || + y == _volData->getHeight() - 1 || + z == _volData->getDepth() - 1) { + _volData->setVoxelAt(x, y, z, 0); + } + } + } + } + } +} bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { return false; } bool result = setVoxelInternal(x, y, z, toValue); - compressVolumeData(); + if (result) { + compressVolumeData(); + } return result; } @@ -298,7 +323,9 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { } } } - compressVolumeData(); + if (result) { + compressVolumeData(); + } return result; } @@ -308,7 +335,7 @@ bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t t } // same as setVoxel but takes a vector rather than 3 floats. - return setVoxel((int)position.x, (int)position.y, (int)position.z, toValue); + return setVoxel(roundf(position.x), roundf(position.y), roundf(position.z), toValue); } bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { @@ -332,7 +359,9 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi } } } - compressVolumeData(); + if (result) { + compressVolumeData(); + } return result; } @@ -354,9 +383,10 @@ public: } bool operator()(PolyVox::SimpleVolume::Sampler& sampler) { - int x = sampler.getPosition().getX(); - int y = sampler.getPosition().getY(); - int z = sampler.getPosition().getZ(); + PolyVox::Vector3DInt32 positionIndex = sampler.getPosition(); + int x = positionIndex.getX(); + int y = positionIndex.getY(); + int z = positionIndex.getZ(); if (!inBounds(_vol, x, y, z)) { return true; @@ -365,8 +395,9 @@ public: if (sampler.getVoxel() == 0) { return true; // keep raycasting } - PolyVox::Vector3DInt32 positionIndex = sampler.getPosition(); - _result = glm::vec4(positionIndex.getX(), positionIndex.getY(), positionIndex.getZ(), 1.0f); + // qDebug() << "RaycastFunctor hit" << x << y << z; + // add {0.5, 0.5, 0.5} to result so it's centered on the voxel + _result = glm::vec4((float)x + 0.5f, (float)y + 0.5f, (float)z + 0.5f, 1.0f); return false; } glm::vec4 _result; @@ -386,7 +417,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return true; } - // the PolyVox ray intersection code requires a near and far point. glm::mat4 wtvMatrix = worldToVoxelMatrix(); glm::vec3 normDirection = glm::normalize(direction); @@ -411,23 +441,29 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o } glm::vec4 result = callback._result; + switch (_voxelSurfaceStyle) { case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - result -= glm::vec4(1, 1, 1, 0); // compensate for the extra voxel border + result -= glm::vec4(1.0f, 1.0f, 1.0f, 0.0f); // compensate for the extra voxel border break; case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: case PolyVoxEntityItem::SURFACE_CUBIC: break; } - result -= glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); + // result -= glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); glm::vec4 intersectedWorldPosition = voxelToWorldMatrix() * result; - distance = glm::distance(glm::vec3(intersectedWorldPosition), origin); + // TODO: use this to find the actual point of intersection rather than using the center of the voxel + // GeometryUtil.h + // bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::quat& rotation, + // const glm::vec3& position, const glm::vec2& dimensions, float& distance);; - face = BoxFace::MIN_X_FACE; // XXX + face = BoxFace::MIN_X_FACE; + + distance = glm::distance(glm::vec3(intersectedWorldPosition), origin); return true; } @@ -436,6 +472,8 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o // compress the data in _volData and save the results. The compressed form is used during // saves to disk and for transmission over the wire void RenderablePolyVoxEntityItem::compressVolumeData() { + auto startTime = usecTimestampNow(); + quint16 voxelXSize = _voxelVolumeSize.x; quint16 voxelYSize = _voxelVolumeSize.y; quint16 voxelZSize = _voxelVolumeSize.z; @@ -486,6 +524,8 @@ void RenderablePolyVoxEntityItem::compressVolumeData() { _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; _needsModelReload = true; + + qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName(); } @@ -525,6 +565,7 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() { } } } + clearEdges(); #ifdef WANT_DEBUG qDebug() << "--------------- voxel decompress ---------------"; @@ -628,7 +669,6 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { offH += 1.0f; } - glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); @@ -691,6 +731,8 @@ void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) { } void RenderablePolyVoxEntityItem::getModel() { + auto startTime = usecTimestampNow(); + // A mesh object to hold the result of surface extraction PolyVox::SurfaceMesh polyVoxMesh; @@ -745,6 +787,8 @@ void RenderablePolyVoxEntityItem::getModel() { #endif _needsModelReload = false; + + qDebug() << "RenderablePolyVoxEntityItem::getModel" << (usecTimestampNow() - startTime) << getName(); } void RenderablePolyVoxEntityItem::render(RenderArgs* args) { @@ -871,3 +915,11 @@ glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3 voxelC glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3 worldCoords) { return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); } + +glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3 voxelCoords) { + return glm::vec3(voxelToLocalMatrix() * glm::vec4(voxelCoords, 1.0f)); +} + +glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3 localCoords) { + return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 1.0f)); +} diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 17c8934577..ffc45beb22 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -71,8 +71,9 @@ public: virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); glm::vec3 getSurfacePositionAdjustment() const; glm::mat4 voxelToWorldMatrix() const; - glm::mat4 voxelToLocalMatrix() const; glm::mat4 worldToVoxelMatrix() const; + glm::mat4 voxelToLocalMatrix() const; + glm::mat4 localToVoxelMatrix() const; virtual ShapeType getShapeType() const; virtual bool isReadyToComputeShape(); @@ -80,6 +81,8 @@ public: virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3 voxelCoords); virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3 worldCoords); + virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3 voxelCoords); + virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3 localCoords); // coords are in voxel-volume space virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue); @@ -110,7 +113,7 @@ private: bool setVoxelInternal(int x, int y, int z, uint8_t toValue); void compressVolumeData(); void decompressVolumeData(); - + void clearEdges(); PolyVox::SimpleVolume* _volData = nullptr; model::Geometry _modelGeometry; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index c3e71cef82..41ca6a91ac 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -697,3 +697,43 @@ glm::vec3 EntityScriptingInterface::worldCoordsToVoxelCoords(const QUuid& entity auto polyVoxEntity = std::dynamic_pointer_cast(entity); return polyVoxEntity->worldCoordsToVoxelCoords(worldCoords); } + +glm::vec3 EntityScriptingInterface::voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords) { + if (!_entityTree) { + return glm::vec3(0.0f); + } + + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToLocalCoords no entity with ID" << entityID; + return glm::vec3(0.0f); + } + + EntityTypes::EntityType entityType = entity->getType(); + if (entityType != EntityTypes::PolyVox) { + return glm::vec3(0.0f); + } + + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->voxelCoordsToLocalCoords(voxelCoords); +} + +glm::vec3 EntityScriptingInterface::localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords) { + if (!_entityTree) { + return glm::vec3(0.0f); + } + + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + qCDebug(entities) << "EntityScriptingInterface::localCoordsToVoxelCoords no entity with ID" << entityID; + return glm::vec3(0.0f); + } + + EntityTypes::EntityType entityType = entity->getType(); + if (entityType != EntityTypes::PolyVox) { + return glm::vec3(0.0f); + } + + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->localCoordsToVoxelCoords(localCoords); +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index e5bff8e80a..a51ebfb61c 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -136,6 +136,8 @@ public slots: Q_INVOKABLE glm::vec3 voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords); Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); + Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords); + Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 563624a1c7..2ba57f85de 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -79,6 +79,8 @@ class PolyVoxEntityItem : public EntityItem { virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3 voxelCoords) { return glm::vec3(0.0f); } virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3 worldCoords) { return glm::vec3(0.0f); } + virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3 voxelCoords) { return glm::vec3(0.0f); } + virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3 localCoords) { return glm::vec3(0.0f); } // coords are in world-space virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) { return false; } From 0230d9397cb04cf09803cc582cd94a0c9083aa09 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 16 Aug 2015 11:04:52 -0700 Subject: [PATCH 045/170] attempt to find exact ray intersection with cubic polyvox -- not working right, yet. --- .../src/RenderablePolyVoxEntityItem.cpp | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 108660f386..760fe5ad16 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -418,6 +418,8 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o } glm::mat4 wtvMatrix = worldToVoxelMatrix(); + glm::mat4 vtwMatrix = voxelToWorldMatrix(); + glm::mat4 vtlMatrix = voxelToLocalMatrix(); glm::vec3 normDirection = glm::normalize(direction); // the PolyVox ray intersection code requires a near and far point. @@ -440,6 +442,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return false; } + // result is in voxel-space coordinates. glm::vec4 result = callback._result; switch (_voxelSurfaceStyle) { @@ -452,18 +455,55 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o break; } - // result -= glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); - glm::vec4 intersectedWorldPosition = voxelToWorldMatrix() * result; + // close-ish, but not right + // glm::vec4 intersectedWorldPosition = vtwMatrix * result; + // distance = glm::distance(glm::vec3(intersectedWorldPosition), origin); - // TODO: use this to find the actual point of intersection rather than using the center of the voxel - // GeometryUtil.h - // bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::quat& rotation, - // const glm::vec3& position, const glm::vec2& dimensions, float& distance);; + glm::vec3 minXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.0f, 0.5f, 0.5f, 0.0f))); + glm::vec3 maxXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(1.0f, 0.5f, 0.5f, 0.0f))); + glm::vec3 minYPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.0f, 0.5f, 0.0f))); + glm::vec3 maxYPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 1.0f, 0.5f, 0.0f))); + glm::vec3 minZPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.5f, 0.0f, 0.0f))); + glm::vec3 maxZPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.5f, 1.0f, 0.0f))); - face = BoxFace::MIN_X_FACE; + glm::vec4 baseDimensions = glm::vec4(1.0, 1.0, 1.0, 0.0); + glm::vec3 worldDimensions = glm::vec3(vtlMatrix * baseDimensions); + glm::vec2 xDimensions = glm::vec2(worldDimensions.y, worldDimensions.z); + glm::vec2 yDimensions = glm::vec2(worldDimensions.x, worldDimensions.z); + glm::vec2 zDimensions = glm::vec2(worldDimensions.x, worldDimensions.y); - distance = glm::distance(glm::vec3(intersectedWorldPosition), origin); + glm::quat minXRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 1.0f, 0.0f, PI_OVER_TWO); + glm::quat maxXRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 1.0f, 0.0f, PI_OVER_TWO); + glm::quat minYRotation = extractRotation(vtwMatrix) * glm::quat(1.0f, 0.0f, 0.0f, PI_OVER_TWO); + glm::quat maxYRotation = extractRotation(vtwMatrix) * glm::quat(1.0f, 0.0f, 0.0f, PI_OVER_TWO); + glm::quat minZRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 0.0f, 1.0f, 0.0f); + glm::quat maxZRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 0.0f, 1.0f, 0.0f); + + float bestDx = FLT_MAX; + bool hit[ 6 ]; + float dx[ 6 ] = {FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX}; + + hit[0] = findRayRectangleIntersection(origin, direction, minXRotation, minXPosition, xDimensions, dx[0]); + hit[1] = findRayRectangleIntersection(origin, direction, maxXRotation, maxXPosition, xDimensions, dx[1]); + hit[2] = findRayRectangleIntersection(origin, direction, minYRotation, minYPosition, yDimensions, dx[2]); + hit[3] = findRayRectangleIntersection(origin, direction, maxYRotation, maxYPosition, yDimensions, dx[3]); + hit[4] = findRayRectangleIntersection(origin, direction, minZRotation, minZPosition, zDimensions, dx[4]); + hit[5] = findRayRectangleIntersection(origin, direction, maxZRotation, maxZPosition, zDimensions, dx[5]); + + bool ok = false; + for (int i = 0; i < 6; i ++) { + if (hit[ i ] && dx[ i ] < bestDx) { + face = (BoxFace)i; + distance = dx[ i ]; + ok = true; + } + } + + if (!ok) { + qWarning() << "RenderablePolyVoxEntityItem::findDetailedRayIntersection -- failed to determine face"; + return false; + } return true; } From a57278ba3c063b6ee576714766d9f395517c4f0c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 16 Aug 2015 11:51:04 -0700 Subject: [PATCH 046/170] ray-picks against cubic-voxels seem right, now --- examples/voxels.js | 2 +- .../src/RenderablePolyVoxEntityItem.cpp | 46 ++++++++----------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/examples/voxels.js b/examples/voxels.js index 0ef9fe9fab..b198ac3948 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -12,7 +12,7 @@ function backUpByOneVoxel(pickRayDirInVoxelSpace, originalVoxelPosition, low, hi if (high - low < 0.001) { print("voxel backup-by-1 failed"); // give up. - return oCoords; + return originalVoxelPosition; } var middle = (low + high) / 2.0; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 760fe5ad16..4819d46e39 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -397,7 +397,9 @@ public: } // qDebug() << "RaycastFunctor hit" << x << y << z; // add {0.5, 0.5, 0.5} to result so it's centered on the voxel - _result = glm::vec4((float)x + 0.5f, (float)y + 0.5f, (float)z + 0.5f, 1.0f); + // _result = glm::vec4((float)x + 0.5f, (float)y + 0.5f, (float)z + 0.5f, 1.0f); + + _result = glm::vec4((float)x, (float)y, (float)z, 1.0f); return false; } glm::vec4 _result; @@ -412,6 +414,8 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o void** intersectedObject, bool precisionPicking) const { + // TODO -- correctly pick against marching-cube generated meshes + if (_needsModelReload || !precisionPicking) { // just intersect with bounding box return true; @@ -443,23 +447,9 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o } // result is in voxel-space coordinates. - glm::vec4 result = callback._result; - - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - result -= glm::vec4(1.0f, 1.0f, 1.0f, 0.0f); // compensate for the extra voxel border - break; - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_CUBIC: - break; - } - - - // close-ish, but not right - // glm::vec4 intersectedWorldPosition = vtwMatrix * result; - // distance = glm::distance(glm::vec3(intersectedWorldPosition), origin); + glm::vec4 result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); + // set up ray tests against each face of the voxel. glm::vec3 minXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.0f, 0.5f, 0.5f, 0.0f))); glm::vec3 maxXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(1.0f, 0.5f, 0.5f, 0.0f))); glm::vec3 minYPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.0f, 0.5f, 0.0f))); @@ -469,16 +459,17 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o glm::vec4 baseDimensions = glm::vec4(1.0, 1.0, 1.0, 0.0); glm::vec3 worldDimensions = glm::vec3(vtlMatrix * baseDimensions); - glm::vec2 xDimensions = glm::vec2(worldDimensions.y, worldDimensions.z); + glm::vec2 xDimensions = glm::vec2(worldDimensions.z, worldDimensions.y); glm::vec2 yDimensions = glm::vec2(worldDimensions.x, worldDimensions.z); glm::vec2 zDimensions = glm::vec2(worldDimensions.x, worldDimensions.y); - glm::quat minXRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 1.0f, 0.0f, PI_OVER_TWO); - glm::quat maxXRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 1.0f, 0.0f, PI_OVER_TWO); - glm::quat minYRotation = extractRotation(vtwMatrix) * glm::quat(1.0f, 0.0f, 0.0f, PI_OVER_TWO); - glm::quat maxYRotation = extractRotation(vtwMatrix) * glm::quat(1.0f, 0.0f, 0.0f, PI_OVER_TWO); - glm::quat minZRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 0.0f, 1.0f, 0.0f); - glm::quat maxZRotation = extractRotation(vtwMatrix) * glm::quat(0.0f, 0.0f, 1.0f, 0.0f); + glm::quat vtwRotation = extractRotation(vtwMatrix); + glm::quat minXRotation = vtwRotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f)); + glm::quat maxXRotation = vtwRotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f)); + glm::quat minYRotation = vtwRotation * glm::quat(glm::vec3(PI_OVER_TWO, 0.0f, 0.0f)); + glm::quat maxYRotation = vtwRotation * glm::quat(glm::vec3(PI_OVER_TWO, 0.0f, 0.0f)); + glm::quat minZRotation = vtwRotation * glm::quat(glm::vec3(0.0f, 0.0f, 0.0f)); + glm::quat maxZRotation = vtwRotation * glm::quat(glm::vec3(0.0f, 0.0f, 0.0f)); float bestDx = FLT_MAX; bool hit[ 6 ]; @@ -497,12 +488,15 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o face = (BoxFace)i; distance = dx[ i ]; ok = true; + bestDx = dx[ i ]; } } if (!ok) { - qWarning() << "RenderablePolyVoxEntityItem::findDetailedRayIntersection -- failed to determine face"; - return false; + // if the attempt to put the ray against one of the voxel-faces fails, just return the center + glm::vec4 intersectedWorldPosition = vtwMatrix * (result + vec4(0.5f, 0.5f, 0.5f, 0.0f)); + distance = glm::distance(glm::vec3(intersectedWorldPosition), origin); + face = BoxFace::MIN_X_FACE; } return true; From c60a8e7dfcbe610d7d51790dd7d1642964c74885 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 16 Aug 2015 12:52:55 -0700 Subject: [PATCH 047/170] minecraft style voxel editing largely works --- examples/voxels.js | 85 ++++--------------- .../src/RenderablePolyVoxEntityItem.cpp | 15 ++-- 2 files changed, 25 insertions(+), 75 deletions(-) diff --git a/examples/voxels.js b/examples/voxels.js index b198ac3948..cc3453202a 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -5,80 +5,34 @@ function floorVector(v) { return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)}; } -function backUpByOneVoxel(pickRayDirInVoxelSpace, originalVoxelPosition, low, high) { - // try to back up along pickRay an amount that changes only one coordinate. this does - // a binary search for a pickRay multiplier that only causes one of the 3 coordinates to change. - var originalVoxelCoords = floorVector(originalVoxelPosition) - if (high - low < 0.001) { - print("voxel backup-by-1 failed"); - // give up. - return originalVoxelPosition; - } - var middle = (low + high) / 2.0; - - var backupVector = Vec3.multiply(pickRayDirInVoxelSpace, -middle); - print("backupVector = " + "{" + backupVector.x + ", " + backupVector.y + ", " + backupVector.z + "}"); - var nPosition = Vec3.sum(originalVoxelPosition, backupVector); // possible neighbor coordinates - var nCoords = floorVector(nPosition); - - print("middle = " + middle + - " -- {" + originalVoxelCoords.x + ", " + originalVoxelCoords.y + ", " + originalVoxelCoords.z + "}" + - " -- {" + nCoords.x + ", " + nCoords.y + ", " + nCoords.z + "}"); - - - if (nCoords.x == originalVoxelCoords.x && - nCoords.y == originalVoxelCoords.y && - nCoords.z == originalVoxelCoords.z) { - // still in the original voxel. back up more... - return backUpByOneVoxel(pickRayDirInVoxelSpace, originalVoxelPosition, middle, high); - } - - if (nCoords.x != originalVoxelCoords.x && - nCoords.y == originalVoxelCoords.y && - nCoords.z == originalVoxelCoords.z) { - return nCoords; - } - if (nCoords.x == originalVoxelCoords.x && - nCoords.y != originalVoxelCoords.y && - nCoords.z == originalVoxelCoords.z) { - return nCoords; - } - if (nCoords.x == originalVoxelCoords.x && - nCoords.y == originalVoxelCoords.y && - nCoords.z != originalVoxelCoords.z) { - return nCoords; - } - - // more than one coordinate changed, but no less... - return backUpByOneVoxel(pickRayDirInVoxelSpace, originalVoxelPosition, low, middle); -} - - function attemptVoxelChange(pickRayDir, intersection) { - var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection); - var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir); - print("voxelPosition = " + voxelPosition.x + ", " + voxelPosition.y + ", " + voxelPosition.z); - print("pickRay = " + pickRayDir.x + ", " + pickRayDir.y + ", " + pickRayDir.z); - print("pickRayInVoxelSpace = " + - pickRayDirInVoxelSpace.x + ", " + pickRayDirInVoxelSpace.y + ", " + pickRayDirInVoxelSpace.z); + var properties = Entities.getEntityProperties(intersection.entityID); + if (properties.type != "PolyVox") { + return false; + } + + var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection); + voxelPosition = Vec3.subtract(voxelPosition, {x: 0.5, y: 0.5, z: 0.5}); + var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir); + pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace); if (controlHeld) { // hold control to erase a voxel - return Entities.setVoxel(intersection.entityID, floorVector(voxelPosition), 0); + var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); + return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0); } else if (shiftHeld) { // hold shift to set all voxels to 255 return Entities.setAllVoxels(intersection.entityID, 255); } else { // no modifier key to add a voxel - var backOneVoxel = backUpByOneVoxel(pickRayDirInVoxelSpace, voxelPosition, 0.0, 1.0); - return Entities.setVoxel(intersection.entityID, backOneVoxel, 255); + var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); + return Entities.setVoxel(intersection.entityID, floorVector(toDrawPosition), 255); } // Entities.setVoxelSphere(id, intersection.intersection, radius, 0) } - function mousePressEvent(event) { if (!event.isLeftButton) { return; @@ -86,22 +40,13 @@ function mousePressEvent(event) { var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking - var properties; if (intersection.intersects) { - properties = Entities.getEntityProperties(intersection.entityID); - print("got accurate pick"); - if (properties.type == "PolyVox") { - if (attemptVoxelChange(pickRay.direction, intersection)) { - return; - } - } else { - print("not a polyvox: " + properties.type); + if (attemptVoxelChange(pickRay.direction, intersection)) { + return; } } - print("trying bounding-box pick"); - // if the PolyVox entity is empty, we can't pick against its "on" voxels. try picking against its // bounding box, instead. intersection = Entities.findRayIntersection(pickRay, false); // bounding box picking diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 4819d46e39..7044eb0b3c 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -395,9 +395,6 @@ public: if (sampler.getVoxel() == 0) { return true; // keep raycasting } - // qDebug() << "RaycastFunctor hit" << x << y << z; - // add {0.5, 0.5, 0.5} to result so it's centered on the voxel - // _result = glm::vec4((float)x + 0.5f, (float)y + 0.5f, (float)z + 0.5f, 1.0f); _result = glm::vec4((float)x, (float)y, (float)z, 1.0f); return false; @@ -506,7 +503,9 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o // compress the data in _volData and save the results. The compressed form is used during // saves to disk and for transmission over the wire void RenderablePolyVoxEntityItem::compressVolumeData() { + #ifdef WANT_DEBUG auto startTime = usecTimestampNow(); + #endif quint16 voxelXSize = _voxelVolumeSize.x; quint16 voxelYSize = _voxelVolumeSize.y; @@ -559,7 +558,9 @@ void RenderablePolyVoxEntityItem::compressVolumeData() { _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; _needsModelReload = true; + #ifdef WANT_DEBUG qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName(); + #endif } @@ -765,7 +766,9 @@ void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) { } void RenderablePolyVoxEntityItem::getModel() { + #ifdef WANT_DEBUG auto startTime = usecTimestampNow(); + #endif // A mesh object to hold the result of surface extraction PolyVox::SurfaceMesh polyVoxMesh; @@ -822,7 +825,9 @@ void RenderablePolyVoxEntityItem::getModel() { _needsModelReload = false; + #ifdef WANT_DEBUG qDebug() << "RenderablePolyVoxEntityItem::getModel" << (usecTimestampNow() - startTime) << getName(); + #endif } void RenderablePolyVoxEntityItem::render(RenderArgs* args) { @@ -951,9 +956,9 @@ glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3 worldC } glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3 voxelCoords) { - return glm::vec3(voxelToLocalMatrix() * glm::vec4(voxelCoords, 1.0f)); + return glm::vec3(voxelToLocalMatrix() * glm::vec4(voxelCoords, 0.0f)); } glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3 localCoords) { - return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 1.0f)); + return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 0.0f)); } From 1ee773a5322ff08fbf0204b995e6be06fad25066 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 16 Aug 2015 14:11:37 -0700 Subject: [PATCH 048/170] don't include interrior voxels in cubic collision hull --- .../src/RenderablePolyVoxEntityItem.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 7044eb0b3c..edccffd397 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -695,6 +695,18 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { for (int y = 0; y < _voxelVolumeSize.y; y++) { for (int x = 0; x < _voxelVolumeSize.x; x++) { if (getVoxel(x, y, z) > 0) { + + if ((x > 0 && getVoxel(x - 1, y, z) == 0) && + (y > 0 && getVoxel(x, y - 1, z) == 0) && + (z > 0 && getVoxel(x, y, z - 1) == 0) && + (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) == 0) && + (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) == 0) && + (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) == 0)) { + // this voxel has neighbors in every cardinal direction, so there's no need + // to include it in the collision hull. + continue; + } + QVector pointsInPart; float offL = -0.5f; From e78d763f85d272d4af889cc08974a6c6ae8df3a5 Mon Sep 17 00:00:00 2001 From: lanac Date: Mon, 17 Aug 2015 12:53:57 +0200 Subject: [PATCH 049/170] shell for Danish translation of Interface --- interface/interface_da.ts | 300 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 interface/interface_da.ts diff --git a/interface/interface_da.ts b/interface/interface_da.ts new file mode 100644 index 0000000000..526eaf7200 --- /dev/null +++ b/interface/interface_da.ts @@ -0,0 +1,300 @@ + + + + + Application + + + Sparse Voxel Octree Files (*.svo) + + + + + Open Script + + + + + JavaScript Files (*.js) + + + + + ChatWindow + + + + Chat + + + + + + Connecting to XMPP... + + + + + + online now: + + + + + day + + %n dag + %n dage + + + + + hour + + %n time + %n timer + + + + + minute + + %n minut + %n minutter + + + + second + + %n second + %n seconds + + + + + %1 online now: + + + + + Dialog + + + + + + Update Required + + + + + + Download + + + + + + Skip Version + + + + + + Close + + + + + Menu + + + Open .ini config file + + + + + + Text files (*.ini) + + + + + Save .ini config file + + + + + PreferencesDialog + + + + Cancel + + + + + + Save all changes + + + + + + + + Avatar + + + + + + <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html> + + + + + + Not showing a name + + + + + + Head + + + + + + Body + + + + + + Advanced Tuning + + + + + + It's not recomended that you play with these settings unless you've looked into exactly what they do. + + + + + + Vertical field of view + + + + + + Lean scale (applies to Faceshift users) + + + + + + Avatar scale <span style=" color:#909090;">(default is 1.0)</span> + + + + + + Pupil dillation + + + + + + Audio Jitter Buffer Samples (0 for automatic) + + + + + + Faceshift eye detection + + + + + + Octree + + + + + + Max packets sent each second + + + + + QObject + + + Loading ... + + + + + Cancel + + + + + RunningScriptsWidget + + + + Form + + + + + + <html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html> + + + + + + <html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html> + + + + + + Reload all + Reload All + + + + + + Stop all + Stop All + + + + + + <html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html> + + + + + + (click a script or use the 1-9 keys to load and run it) + + + + + + There are no scripts currently running. + + + + From af11e97daad8d8aa4a5c06e8d8cf7f59c683debb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 17 Aug 2015 08:49:00 -0700 Subject: [PATCH 050/170] fix broken optimisation for skipping internal voxels when making collision hull --- .../src/RenderablePolyVoxEntityItem.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index edccffd397..5c0a7888e5 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -696,12 +696,12 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { for (int x = 0; x < _voxelVolumeSize.x; x++) { if (getVoxel(x, y, z) > 0) { - if ((x > 0 && getVoxel(x - 1, y, z) == 0) && - (y > 0 && getVoxel(x, y - 1, z) == 0) && - (z > 0 && getVoxel(x, y, z - 1) == 0) && - (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) == 0) && - (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) == 0) && - (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) == 0)) { + if ((x > 0 && getVoxel(x - 1, y, z) > 0) && + (y > 0 && getVoxel(x, y - 1, z) > 0) && + (z > 0 && getVoxel(x, y, z - 1) > 0) && + (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) > 0) && + (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) > 0) && + (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) > 0)) { // this voxel has neighbors in every cardinal direction, so there's no need // to include it in the collision hull. continue; From 014bf661fa92de5c1c9126783a8d3cb84ed654fd Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 17 Aug 2015 11:18:25 -0700 Subject: [PATCH 051/170] bump packet version --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 5562490344..db2a426696 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType::Value packetType) { case EntityAdd: case EntityEdit: case EntityData: - return VERSION_ENTITIES_POLYLINE; + return VERSION_OCTREE_CENTERED_ORIGIN; case AvatarData: return 12; default: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 1aeadb1af9..75d8105dca 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -142,5 +142,6 @@ const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE const PacketVersion VERSION_ENTITIES_NEW_PROTOCOL_LAYER = 35; const PacketVersion VERSION_POLYVOX_TEXTURES = 36; const PacketVersion VERSION_ENTITIES_POLYLINE = 37; +const PacketVersion VERSION_OCTREE_CENTERED_ORIGIN = 38; #endif // hifi_PacketHeaders_h \ No newline at end of file From e43dc3f9fed195c4ee99c1ccc3db81e92a9e8351 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 18 Aug 2015 03:21:48 +0200 Subject: [PATCH 052/170] Completed implementation of WebSocket and WebSocketServer --- libraries/script-engine/src/ScriptEngine.cpp | 2 + .../script-engine/src/WebSocketClass.cpp | 83 ++++++++++++++----- libraries/script-engine/src/WebSocketClass.h | 28 +++++-- .../src/WebSocketServerClass.cpp | 27 +++++- .../script-engine/src/WebSocketServerClass.h | 21 ++++- 5 files changed, 124 insertions(+), 37 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index bde0643c51..c629529382 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -361,6 +361,8 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, inputControllerToScriptValue, inputControllerFromScriptValue); qScriptRegisterMetaType(this, avatarDataToScriptValue, avatarDataFromScriptValue); qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue); + qScriptRegisterMetaType(this, webSocketToScriptValue, webSocketFromScriptValue); + qScriptRegisterMetaType(this, qWSCloseCodeToScriptValue, qWSCloseCodeFromScriptValue); registerGlobalObject("Script", this); registerGlobalObject("Audio", &AudioScriptingInterface::getInstance()); diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index f116c3a0e4..654b4746a8 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -16,13 +16,25 @@ #include "WebSocketClass.h" WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) : - _engine(engine) + _engine(engine), + _webSocket(new QWebSocket()) { - connect(&_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose); - connect(&_webSocket, SIGNAL(error()), this, SLOT(handleOnError())); - connect(&_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage); - connect(&_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen); - _webSocket.open(url); + initialize(); + _webSocket->open(url); +} + +WebSocketClass::WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket) : + _engine(engine), + _webSocket(qWebSocket) +{ + initialize(); +} + +void WebSocketClass::initialize() { + connect(_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose); + connect(_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage); + connect(_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen); + _binaryType = "blob"; } QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { @@ -39,26 +51,37 @@ WebSocketClass::~WebSocketClass() { } void WebSocketClass::send(QScriptValue message) { - _webSocket.sendTextMessage(message.toString()); + _webSocket->sendTextMessage(message.toString()); +} + +void WebSocketClass::close() { + this->close(QWebSocketProtocol::CloseCode::CloseCodeNormal); +} + +void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode) { + this->close(closeCode, QStringLiteral("")); } void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reason) { - _webSocket.close(closeCode, reason); + _webSocket->close(closeCode, reason); } void WebSocketClass::handleOnClose() { - if (_onCloseEvent.isFunction()) { - //QScriptValueList args; - //args << ("received: " + message); - _onCloseEvent.call();//(QScriptValue(), args); + bool hasError = false; + if (_webSocket->error() != QAbstractSocket::SocketError::UnknownSocketError) { + hasError = true; + if (_onErrorEvent.isFunction()) { + _onErrorEvent.call(); + } } -} - -void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) { - if (_onErrorEvent.isFunction()) { - // QScriptValueList args; - //args << ("received: " + message); - _onErrorEvent.call();/// QScriptValue(), args); + if (_onCloseEvent.isFunction()) { + QScriptValueList args; + QScriptValue arg = _engine->newObject(); + arg.setProperty("code", hasError ? QWebSocketProtocol::CloseCode::CloseCodeAbnormalDisconnection : _webSocket->closeCode()); + arg.setProperty("reason", _webSocket->closeReason()); + arg.setProperty("wasClean", !hasError); + args << arg; + _onCloseEvent.call(QScriptValue(), args); } } @@ -74,8 +97,22 @@ void WebSocketClass::handleOnMessage(const QString& message) { void WebSocketClass::handleOnOpen() { if (_onOpenEvent.isFunction()) { - //QScriptValueList args; - //args << ("received: " + message); - _onOpenEvent.call();// QScriptValue(), args); + _onOpenEvent.call(); } -} \ No newline at end of file +} + +QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode &closeCode) { + return closeCode; +} + +void qWSCloseCodeFromScriptValue(const QScriptValue &object, QWebSocketProtocol::CloseCode &closeCode) { + closeCode = (QWebSocketProtocol::CloseCode)object.toUInt16(); +} + +QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in) { + return engine->newQObject(in); +} + +void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out) { + out = qobject_cast(object.toQObject()); +} diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h index 6f0e3729c0..fc70a72883 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -38,6 +38,7 @@ class WebSocketClass : public QObject { public: WebSocketClass(QScriptEngine* engine, QString url); + WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket); ~WebSocketClass(); static QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine); @@ -57,19 +58,18 @@ public: void setBinaryType(QString binaryType) { _binaryType = binaryType; } QString getBinaryType() { return _binaryType; } - // extensions is a empty string until supported in QT + // extensions is a empty string until supported in QT WebSockets QString getExtensions() { return QString(); } - // protocol is a empty string until supported in QT + // protocol is a empty string until supported in QT WebSockets QString getProtocol() { return QString(); } - //TODO: find buffered amount ulong getBufferedAmount() { return 0; } - QString getURL() { return _webSocket.requestUrl().toDisplayString(); } + QString getURL() { return _webSocket->requestUrl().toDisplayString(); } uint getReadyState() { - switch (_webSocket.state()) { + switch (_webSocket->state()) { case QAbstractSocket::SocketState::HostLookupState: case QAbstractSocket::SocketState::ConnectingState: return CONNECTING; @@ -85,19 +85,25 @@ public: void setOnClose(QScriptValue eventFunction) { _onCloseEvent = eventFunction; } QScriptValue getOnClose() { return _onCloseEvent; } + void setOnError(QScriptValue eventFunction) { _onErrorEvent = eventFunction; } QScriptValue getOnError() { return _onErrorEvent; } + void setOnMessage(QScriptValue eventFunction) { _onMessageEvent = eventFunction; } QScriptValue getOnMessage() { return _onMessageEvent; } + void setOnOpen(QScriptValue eventFunction) { _onOpenEvent = eventFunction; } QScriptValue getOnOpen() { return _onOpenEvent; } public slots: void send(QScriptValue message); + + void close(); + void close(QWebSocketProtocol::CloseCode closeCode); void close(QWebSocketProtocol::CloseCode closeCode, QString reason); private: - QWebSocket _webSocket; + QWebSocket* _webSocket; QScriptEngine* _engine; QScriptValue _onCloseEvent; @@ -107,12 +113,20 @@ private: QString _binaryType; + void initialize(); + private slots: void handleOnClose(); - void handleOnError(QAbstractSocket::SocketError error); void handleOnMessage(const QString& message); void handleOnOpen(); }; +Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode); +QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode& closeCode); +void qWSCloseCodeFromScriptValue(const QScriptValue& object, QWebSocketProtocol::CloseCode& closeCode); + +QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in); +void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out); + #endif // hifi_WebSocketClass_h diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp index 86f6506375..7981d67de3 100644 --- a/libraries/script-engine/src/WebSocketServerClass.cpp +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -14,11 +14,13 @@ #include "ScriptEngine.h" #include "WebSocketServerClass.h" -WebSocketServerClass::WebSocketServerClass(QScriptEngine* engine, const QString& serverName, quint16 port) : +WebSocketServerClass::WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port) : _engine(engine), _webSocketServer(serverName, QWebSocketServer::SslMode::NonSecureMode) { - _webSocketServer.listen(QHostAddress::Any, port); + if (_webSocketServer.listen(QHostAddress::Any, port)) { + connect(&_webSocketServer, &QWebSocketServer::newConnection, this, &WebSocketServerClass::onNewConnection); + } } QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine) { @@ -33,12 +35,31 @@ QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptE if (portOption.isValid() && portOption.isNumber()) { port = portOption.toNumber(); } + QScriptValue serverNameOption = options.property(QStringLiteral("serverName")); + if (serverNameOption.isValid() && serverNameOption.isString()) { + serverName = serverNameOption.toString(); + } } return engine->newQObject(new WebSocketServerClass(engine, serverName, port)); } WebSocketServerClass::~WebSocketServerClass() { if (_webSocketServer.isListening()) { - _webSocketServer.close(); + close(); } } + +void WebSocketServerClass::onNewConnection() { + WebSocketClass* newClient = new WebSocketClass(_engine, _webSocketServer.nextPendingConnection()); + _clients << newClient; + emit newConnection(newClient); +} + +void WebSocketServerClass::close() { + foreach(WebSocketClass* client, _clients) { + if (client->getReadyState() != WebSocketClass::ReadyState::CLOSED) { + client->close(QWebSocketProtocol::CloseCode::CloseCodeGoingAway, "Server closing."); + } + } + _webSocketServer.close(); +} diff --git a/libraries/script-engine/src/WebSocketServerClass.h b/libraries/script-engine/src/WebSocketServerClass.h index 18fcdc693e..cad993a508 100644 --- a/libraries/script-engine/src/WebSocketServerClass.h +++ b/libraries/script-engine/src/WebSocketServerClass.h @@ -15,24 +15,37 @@ #include #include #include +#include "WebSocketClass.h" class WebSocketServerClass : public QObject { Q_OBJECT + Q_PROPERTY(QString url READ getURL) + Q_PROPERTY(quint16 port READ getPort) + Q_PROPERTY(bool listening READ isListening) public: - WebSocketServerClass(QScriptEngine* engine, const QString& serverName, quint16 port); + WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port); ~WebSocketServerClass(); + QString getURL() { return _webSocketServer.serverUrl().toDisplayString(); } + quint16 getPort() { return _webSocketServer.serverPort(); } + bool isListening() { return _webSocketServer.isListening(); } + static QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine); +public slots: + void close(); + private: QWebSocketServer _webSocketServer; QScriptEngine* _engine; + QList _clients; + +private slots: + void onNewConnection(); signals: - void newConnection(); - - + void newConnection(WebSocketClass* client); }; From 29c857f1fa1d95913e7b3a0fc949c8e7fe5a21e6 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 18 Aug 2015 03:24:26 +0200 Subject: [PATCH 053/170] - unitTests for WebSockets + WebSocketServer (added some asynchronous functionality to the unitTest.js library , previous functionality intact) --- examples/libraries/unitTest.js | 107 +++++++++++++++--- .../utilities/diagnostics/testWebSocket.js | 100 ++++++++++++++++ 2 files changed, 192 insertions(+), 15 deletions(-) create mode 100644 examples/utilities/diagnostics/testWebSocket.js diff --git a/examples/libraries/unitTest.js b/examples/libraries/unitTest.js index beb3387898..d65cd780bd 100644 --- a/examples/libraries/unitTest.js +++ b/examples/libraries/unitTest.js @@ -11,17 +11,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var test = function(name, func) { +test = function(name, func, timeout) { print("Running test: " + name); - - var unitTest = new UnitTest(name, func); - - try { - unitTest.run(); + var unitTest = new UnitTest(name, func, timeout); + unitTest.run(function(unitTest) { print(" Success: " + unitTest.numAssertions + " assertions passed"); - } catch (error) { + }, function(unitTest, error) { print(" Failure: " + error.name + " " + error.message); - } + }); }; AssertionException = function(expected, actual, message) { @@ -36,13 +33,86 @@ UnthrownException = function(message) { this.name = 'UnthrownException'; }; -UnitTest = function(name, func) { - this.numAssertions = 0; - this.func = func; +TimeoutException = function() { + print("Creating exception"); + this.message = "UnitTest timed out\n"; + this.name = 'TimeoutException'; }; -UnitTest.prototype.run = function() { - this.func(); +SequentialUnitTester = function() { + this.tests = []; + this.testIndex = -1; +}; + +SequentialUnitTester.prototype.addTest = function(name, func, timeout) { + var _this = this; + this.tests.push(function() { + print("Running test: " + name); + var unitTest = new UnitTest(name, func, timeout); + unitTest.run(function(unitTest) { + print(" Success: " + unitTest.numAssertions + " assertions passed"); + _this._nextTest(); + }, function(unitTest, error) { + print(" Failure: " + error.name + " " + error.message); + _this._nextTest(); + }); + }); +}; + +SequentialUnitTester.prototype._nextTest = function() { + this.testIndex++; + if (this.testIndex < this.tests.length) { + this.tests[this.testIndex](); + return; + } + print("Completed all UnitTests"); +}; + +SequentialUnitTester.prototype.run = function() { + this._nextTest(); +}; + +UnitTest = function(name, func, timeout) { + this.numAssertions = 0; + this.func = func; + this.timeout = timeout; +}; + +UnitTest.prototype.run = function(successCallback, failureCallback) { + var _this = this; + this.successCallback = successCallback; + this.failureCallback = failureCallback; + if (this.timeout !== undefined) { + this.timeoutTimer = Script.setTimeout(function() { + _this.failureCallback(this, new TimeoutException()); + }, this.timeout); + } + try { + this.func(); + if (this.timeout === undefined) { + successCallback(this); + } + } catch (exception) { + this.handleException(exception); + } +}; + +UnitTest.prototype.registerCallbackFunction = function(func) { + var _this = this; + return function(one, two, three, four, five, six) { + try { + func(one, two, three, four, five, six); + } catch (exception) { + _this.handleException(exception); + } + }; +}; + +UnitTest.prototype.handleException = function(exception) { + if (this.timeout !== undefined) { + Script.clearTimeout(this.timeoutTimer); + } + this.failureCallback(this, exception); }; UnitTest.prototype.assertNotEquals = function(expected, actual, message) { @@ -83,7 +153,7 @@ UnitTest.prototype.assertNull = function(value, message) { UnitTest.prototype.arrayEqual = function(array1, array2, message) { this.numAssertions++; if (array1.length !== array2.length) { - throw new AssertionException(array1.length , array2.length , message); + throw new AssertionException(array1.length, array2.length , message); } for (var i = 0; i < array1.length; ++i) { if (array1[i] !== array2[i]) { @@ -101,4 +171,11 @@ UnitTest.prototype.raises = function(func, message) { } throw new UnthrownException(message); -} \ No newline at end of file +} + +UnitTest.prototype.ready = function() { + if (this.timeout !== undefined) { + Script.clearTimeout(this.timeoutTimer); + this.successCallback(this); + } +} diff --git a/examples/utilities/diagnostics/testWebSocket.js b/examples/utilities/diagnostics/testWebSocket.js new file mode 100644 index 0000000000..03b9b6b405 --- /dev/null +++ b/examples/utilities/diagnostics/testWebSocket.js @@ -0,0 +1,100 @@ +// +// testWebSocket.js +// examples +// +// Created by Thijs Wenker on 8/18/15 +// Copyright 2015 High Fidelity, Inc. +// +// WebSocket and WebSocketServer Tests +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("../../libraries/unitTest.js"); + +// We set the unit testing timeout to 1000 milliseconds by default. Please increase if the test fails due to a slow connection. +const UNITTEST_TIMEOUT = 1000; +const WEBSOCKET_PING_URL = "ws://echo.websocket.org"; +// Please do not register the following domain + gTLD: +const WEBSOCKET_INVALID_URL = "ws://thisisnotavaliddomainname.invalid"; +const TEST_MESSAGE = "This is a test message."; + +var unitTests = new SequentialUnitTester(); + +unitTests.addTest("Test default WebSocket values", function(finished) { + var _this = this; + var webSocket = new WebSocket(WEBSOCKET_PING_URL); + + webSocket.onmessage = this.registerCallbackFunction(function(event) { + _this.assertEquals(TEST_MESSAGE, event.data, "event.data should be '" + TEST_MESSAGE + "'"); + webSocket.close(); + }); + webSocket.onopen = this.registerCallbackFunction(function(event) { + _this.assertEquals(webSocket.OPEN, webSocket.readyState, "readyState should be OPEN"); + webSocket.send(TEST_MESSAGE); + }); + webSocket.onclose = this.registerCallbackFunction(function(event) { + _this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED"); + _this.ready(); + }); + this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING"); + this.assertEquals("blob", webSocket.binaryType, "binaryType should be 'blob'"); + this.assertEquals(0, webSocket.bufferedAmount, "bufferedAmount should be 0"); + this.assertEquals("", webSocket.extensions, "extensions should be an empty string by default"); + this.assertEquals("", webSocket.protocol, "protocol should be an empty string by default"); + this.assertEquals(WEBSOCKET_PING_URL, webSocket.url, "url should be '" + WEBSOCKET_PING_URL + "'"); +}, UNITTEST_TIMEOUT); + +unitTests.addTest("Test WebSocket invalid URL", function(finished) { + var _this = this; + var webSocket = new WebSocket(WEBSOCKET_INVALID_URL); + var hadError = false; + webSocket.onerror = this.registerCallbackFunction(function() { + hadError = true; + }); + webSocket.onclose = this.registerCallbackFunction(function(event) { + _this.assertEquals(true, hadError, "hadError should be true"); + _this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED"); + _this.ready(); + }); + this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING"); + this.assertEquals(WEBSOCKET_INVALID_URL, webSocket.url, "url should be '" + WEBSOCKET_INVALID_URL + "'"); +}, UNITTEST_TIMEOUT); + + +unitTests.addTest("Test WebSocketServer with three clients", function(finished) { + var _this = this; + const NUMBER_OF_CLIENTS = 3; + var connectedClients = 0; + var respondedClients = 0; + var webSocketServer = new WebSocketServer(); + _this.assertEquals(true, webSocketServer.listening, "listening should be true"); + webSocketServer.newConnection.connect(this.registerCallbackFunction(function(newClient) { + connectedClients++; + newClient.onmessage = _this.registerCallbackFunction(function(event) { + var data = JSON.parse(event.data); + _this.assertEquals(TEST_MESSAGE, data.message, "data.message should be '" + TEST_MESSAGE + "'"); + respondedClients++; + if (respondedClients === NUMBER_OF_CLIENTS) { + webSocketServer.close(); + _this.assertEquals(false, webSocketServer.listening, "listening should be false"); + _this.ready(); + } + }); + newClient.send(JSON.stringify({message: TEST_MESSAGE, client: connectedClients})); + })); + var newSocket1 = new WebSocket(webSocketServer.url); + newSocket1.onmessage = this.registerCallbackFunction(function(event) { + newSocket1.send(event.data); + }); + var newSocket2 = new WebSocket(webSocketServer.url); + newSocket2.onmessage = this.registerCallbackFunction(function(event) { + newSocket2.send(event.data); + }); + var newSocket3 = new WebSocket(webSocketServer.url); + newSocket3.onmessage = this.registerCallbackFunction(function(event) { + newSocket3.send(event.data); + }); +}, UNITTEST_TIMEOUT); + +unitTests.run(); From 44399da41cc6e315341916f3ee74b4f59a671730 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 18 Aug 2015 14:54:21 +0200 Subject: [PATCH 054/170] Removed the minimize button from the tool window. Close it to hide it. Fixes bug where ToolWindow hides forever. --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4e790bfa88..35206c64de 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -560,7 +560,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _toolWindow = new ToolWindow(); - _toolWindow->setWindowFlags(_toolWindow->windowFlags() | Qt::WindowStaysOnTopHint); + _toolWindow->setWindowFlags((_toolWindow->windowFlags() | Qt::WindowStaysOnTopHint) & ~Qt::WindowMinimizeButtonHint); _toolWindow->setWindowTitle("Tools"); _offscreenContext->makeCurrent(); From 00aa336a2bda14ee9eb8279640f13c6ba53331f2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Aug 2015 10:01:00 -0700 Subject: [PATCH 055/170] Add further temporary debug for tracking down invalid scale value assert --- interface/src/avatar/Avatar.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 55809646c0..0adf2589cd 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -688,6 +688,23 @@ glm::vec3 Avatar::getDisplayNamePosition() const { const float HEAD_PROPORTION = 0.75f; namePosition = _position + getBodyUpDirection() * (getBillboardSize() * HEAD_PROPORTION); } +#ifdef DEBUG + // TODO: Temporary logging to track cause of invalid scale value; remove once cause has been fixed. + // See other TODO below. + if (glm::isnan(namePosition.x) || glm::isnan(namePosition.y) || glm::isnan(namePosition.z) + || glm::isinf(namePosition.x) || glm::isinf(namePosition.y) || glm::isinf(namePosition.z)) { + qDebug() << "namePosition =" << namePosition; + glm::vec3 tempPosition(0.0f); + if (getSkeletonModel().getNeckPosition(tempPosition)) { + qDebug() << "getBodyUpDirection() =" << getBodyUpDirection(); + qDebug() << "getHeadHeight() =" << getHeadHeight(); + } else { + qDebug() << "_position =" << _position; + qDebug() << "getBodyUpDirection() =" << getBodyUpDirection(); + qDebug() << "getBillboardSize() =" << getBillboardSize(); + } + } +#endif return namePosition; } @@ -722,7 +739,8 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa // Compute correct scale to apply float scale = DESIRED_HIGHT_ON_SCREEN / (fontSize * pixelHeight) * devicePixelRatio; #ifdef DEBUG - // TODO: Temporary logging to track cause of invalid scale vale; remove once cause has been fixed. + // TODO: Temporary logging to track cause of invalid scale value; remove once cause has been fixed. + // Problem is probably due to an invalid getDisplayNamePosition(). See extra logging above. if (scale == 0.0f || glm::isnan(scale) || glm::isinf(scale)) { if (scale == 0.0f) { qDebug() << "ASSERT because scale == 0.0f"; @@ -733,6 +751,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa if (glm::isinf(scale)) { qDebug() << "ASSERT because isinf(scale)"; } + qDebug() << "textPosition =" << textPosition; qDebug() << "windowSizeY =" << windowSizeY; qDebug() << "p1.y =" << p1.y; qDebug() << "p1.w =" << p1.w; From f6815c215a5b4afa79747b87e3b942177d1eb665 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Aug 2015 10:59:49 -0700 Subject: [PATCH 056/170] Fix edit toolbar moving to top left of screen --- examples/libraries/toolBars.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/libraries/toolBars.js b/examples/libraries/toolBars.js index 16c68a73fb..abe8de8cc3 100644 --- a/examples/libraries/toolBars.js +++ b/examples/libraries/toolBars.js @@ -362,8 +362,11 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit this.fractionKey = optionalPersistenceKey + '.fraction'; this.save = function () { var screenSize = Controller.getViewportDimensions(); - var fraction = {x: that.x / screenSize.x, y: that.y / screenSize.y}; - Settings.setValue(this.fractionKey, JSON.stringify(fraction)); + if (screenSize.x > 0 && screenSize.y > 0) { + // Guard against invalid screen size that can occur at shut-down. + var fraction = {x: that.x / screenSize.x, y: that.y / screenSize.y}; + Settings.setValue(this.fractionKey, JSON.stringify(fraction)); + } } } else { this.save = function () { }; // Called on move. Can be overriden or extended by clients. From 2dc2757ecabf501ed5c92f4c1d069b028d172b50 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 18 Aug 2015 13:03:54 -0700 Subject: [PATCH 057/170] code review --- .../src/RenderablePolyVoxEntityItem.cpp | 14 +++++++------- .../src/RenderablePolyVoxEntityItem.h | 8 ++++---- libraries/entities/src/PolyVoxEntityItem.h | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5c0a7888e5..5d139a719a 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -659,9 +659,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { uint32_t p1Index = *(it++); uint32_t p2Index = *(it++); - const glm::vec3 p0 = vertexBufferView.get(p0Index); - const glm::vec3 p1 = vertexBufferView.get(p1Index); - const glm::vec3 p2 = vertexBufferView.get(p2Index); + const glm::vec3& p0 = vertexBufferView.get(p0Index); + const glm::vec3& p1 = vertexBufferView.get(p1Index); + const glm::vec3& p2 = vertexBufferView.get(p2Index); glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); @@ -959,18 +959,18 @@ namespace render { } } -glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3 voxelCoords) { +glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const { return glm::vec3(voxelToWorldMatrix() * glm::vec4(voxelCoords, 1.0f)); } -glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3 worldCoords) { +glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3& worldCoords) const { return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); } -glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3 voxelCoords) { +glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const { return glm::vec3(voxelToLocalMatrix() * glm::vec4(voxelCoords, 0.0f)); } -glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3 localCoords) { +glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& localCoords) const { return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 0.0f)); } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index ffc45beb22..e2fcdedc31 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -79,10 +79,10 @@ public: virtual bool isReadyToComputeShape(); virtual void computeShapeInfo(ShapeInfo& info); - virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3 voxelCoords); - virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3 worldCoords); - virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3 voxelCoords); - virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3 localCoords); + virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const; + virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3& worldCoords) const; + virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const; + virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3& localCoords) const; // coords are in voxel-volume space virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue); diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 2ba57f85de..9fbaade407 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -77,10 +77,10 @@ class PolyVoxEntityItem : public EntityItem { virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { return false; } virtual bool setVoxelInVolume(glm::vec3 position, uint8_t toValue) { return false; } - virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3 voxelCoords) { return glm::vec3(0.0f); } - virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3 worldCoords) { return glm::vec3(0.0f); } - virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3 voxelCoords) { return glm::vec3(0.0f); } - virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3 localCoords) { return glm::vec3(0.0f); } + virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const { return glm::vec3(0.0f); } + virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3& worldCoords) const { return glm::vec3(0.0f); } + virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const { return glm::vec3(0.0f); } + virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3& localCoords) const { return glm::vec3(0.0f); } // coords are in world-space virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) { return false; } From 944734fed2d38ad47ed25a9d0f33d90b21b0eddb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 18 Aug 2015 13:59:39 -0700 Subject: [PATCH 058/170] Remove focus highlight on delete entity --- interface/src/Application.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c9c3b94e03..a9997c7426 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -719,6 +719,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : } }); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, + [=](const EntityItemID& entityItemID) { + if (entityItemID == _keyboardFocusedItem) { + _keyboardFocusedItem = UNKNOWN_ENTITY_ID; + if (_keyboardFocusHighlight) { + _keyboardFocusHighlight->setVisible(false); + } + } + }); + // If the user clicks somewhere where there is NO entity at all, we will release focus connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity, [=](const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId) { From 1b6538cc59fa154fa9302247cb5770b003a71072 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 19 Aug 2015 00:31:40 +0200 Subject: [PATCH 059/170] fixes extra qualification errors Linux --- libraries/script-engine/src/WebSocketClass.cpp | 2 +- libraries/script-engine/src/WebSocketServerClass.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 654b4746a8..7283f2c47f 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -37,7 +37,7 @@ void WebSocketClass::initialize() { _binaryType = "blob"; } -QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { QString url; QScriptValue callee = context->callee(); if (context->argumentCount() == 1) { diff --git a/libraries/script-engine/src/WebSocketServerClass.h b/libraries/script-engine/src/WebSocketServerClass.h index cad993a508..972bf9c032 100644 --- a/libraries/script-engine/src/WebSocketServerClass.h +++ b/libraries/script-engine/src/WebSocketServerClass.h @@ -31,7 +31,7 @@ public: quint16 getPort() { return _webSocketServer.serverPort(); } bool isListening() { return _webSocketServer.isListening(); } - static QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine); + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); public slots: void close(); From 5fcbd37e439c95c8c0d983430a27cc165b3457aa Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 18 Aug 2015 15:45:17 -0700 Subject: [PATCH 060/170] possible fix for invisible other's-avatar bug --- interface/src/avatar/Avatar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 0adf2589cd..4d8e3d99dd 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -563,6 +563,9 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { } void Avatar::fixupModelsInScene() { + if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { + return; + } // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene From 7f8506acc0c4851a090dc74986d994c79f3415d1 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 19 Aug 2015 00:54:00 +0200 Subject: [PATCH 061/170] fix. switchup. --- libraries/script-engine/src/WebSocketClass.cpp | 2 +- libraries/script-engine/src/WebSocketClass.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 7283f2c47f..654b4746a8 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -37,7 +37,7 @@ void WebSocketClass::initialize() { _binaryType = "blob"; } -QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { QString url; QScriptValue callee = context->callee(); if (context->argumentCount() == 1) { diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h index fc70a72883..e053257e3d 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -41,7 +41,7 @@ public: WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket); ~WebSocketClass(); - static QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine); + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); enum ReadyState { CONNECTING = 0, From eba446e7f66010436b70a331394f5a507f0b8103 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 17 Aug 2015 10:52:05 -0700 Subject: [PATCH 062/170] Updating to the new version of the sixense library --- CMakeLists.txt | 1 + cmake/externals/sixense/CMakeLists.txt | 61 +++++++++++++------------- cmake/modules/FindSixense.cmake | 2 +- libraries/input-plugins/CMakeLists.txt | 8 ++-- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38bcb42e26..41b88e4cea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,7 @@ option(GET_OPENVR "Get OpenVR library automatically as external project" 1) option(GET_BOOSTCONFIG "Get Boost-config library automatically as external project" 1) option(GET_OGLPLUS "Get OGLplus library automatically as external project" 1) option(GET_GLEW "Get GLEW library automatically as external project" 1) +option(GET_SIXENSE "Get Sixense library automatically as external project" 1) option(USE_NSIGHT "Attempt to find the nSight libraries" 1) diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt index c80b492509..d0778774cb 100644 --- a/cmake/externals/sixense/CMakeLists.txt +++ b/cmake/externals/sixense/CMakeLists.txt @@ -7,54 +7,55 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) ExternalProject_Add( ${EXTERNAL_NAME} - URL ./SixenseSDK_062612.zip - URL_MD5 10cc8dc470d2ac1244a88cf04bc549cc + URL http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip + URL_MD5 752a3901f334124e9cffc2ba4136ef7d CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" LOG_DOWNLOAD 1 ) -if (APPLE) - find_library(SIXENSE_LIBRARY_RELEASE lib/osx_x64/release_dll/libsixense_x64.dylib HINTS ${SIXENSE_SEARCH_DIRS}) - find_library(SIXENSE_LIBRARY_DEBUG lib/osx_x64/debug_dll/libsixensed_x64.dylib HINTS ${SIXENSE_SEARCH_DIRS}) -elseif (UNIX) - find_library(SIXENSE_LIBRARY_RELEASE lib/linux_x64/release/libsixense_x64.so HINTS ${SIXENSE_SEARCH_DIRS}) - # find_library(SIXENSE_LIBRARY_DEBUG lib/linux_x64/debug/libsixensed_x64.so HINTS ${SIXENSE_SEARCH_DIRS}) -elseif (WIN32) -endif () - - - ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) if (WIN32) - - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(ARCH_DIR "x64") - set(ARCH_SUFFIX "_x64") - else() - set(ARCH_DIR "Win32") - set(ARCH_SUFFIX "") - endif() + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(ARCH_DIR "x64/VS2013") + set(ARCH_SUFFIX "_x64") + else() + set(ARCH_DIR "Win32/VS2013") + set(ARCH_SUFFIX "") + endif() + + set(LIB_DIR "${SOURCE_DIR}/lib/${ARCH_DIR}") + set(BIN_DIR "${SOURCE_DIR}/bin/${ARCH_DIR}") + set(SIXENSE_LIBRARY_RELEASE "${LIB_DIR}/release_dll/sixense${ARCH_SUFFIX}.lib" CACHE FILEPATH "Sixense release lib") + set(SIXENSE_LIBRARY_DEBUG "${LIB_DIR}/debug_dll/sixensed${ARCH_SUFFIX}.lib" CACHE FILEPATH "Sixense debug lib") + set(SIXENSE_RELEASE_DLL_PATH "${BIN_DIR}/release_dll" CACHE FILEPATH "Sixense release DLL path") + set(SIXENSE_DEBUG_DLL_PATH "${BIN_DIR}/debug_dll" CACHE FILEPATH "Sixense debug DLL path") # FIXME need to account for different architectures - set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${SOURCE_DIR}/lib/${ARCH_DIR}/release_dll/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) - add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win32) + add_paths_to_fixup_libs(${SIXENSE_DEBUG_DLL_PATH}) + add_paths_to_fixup_libs(${SIXENSE_RELEASE_DLL_PATH}) elseif(APPLE) - # FIXME need to account for different architectures - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx32/libopenvr_api.dylib CACHE TYPE INTERNAL) - add_paths_to_fixup_libs(${SOURCE_DIR}/bin/osx32) + set(ARCH_DIR "osx_x64") + set(ARCH_SUFFIX "_x64") + set(LIB_DIR "${SOURCE_DIR}/lib/${ARCH_DIR}") + + set(SIXENSE_LIBRARY_RELEASE "${LIB_DIR}/release_dll/libsixense${ARCH_SUFFIX}.dylib" CACHE FILEPATH "Sixense release lib") + set(SIXENSE_LIBRARY_DEBUG "${LIB_DIR}/debug_dll/libsixensed${ARCH_SUFFIX}.dylib" CACHE FILEPATH "Sixense debug lib") elseif(NOT ANDROID) - # FIXME need to account for different architectures - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux32/libopenvr_api.so CACHE TYPE INTERNAL) - add_paths_to_fixup_libs(${SOURCE_DIR}/bin/linux32) - + set(ARCH_DIR "linux_x64") + set(ARCH_SUFFIX "_x64") + set(LIB_DIR "${SOURCE_DIR}/lib/${ARCH_DIR}") + + set(SIXENSE_LIBRARY_RELEASE "${LIB_DIR}/release/libsixense${ARCH_SUFFIX}.so" CACHE FILEPATH "Sixense release lib") + set(SIXENSE_LIBRARY_DEBUG "${LIB_DIR}/debug/libsixensed${ARCH_SUFFIX}.so" CACHE FILEPATH "Sixense debug lib") + endif() diff --git a/cmake/modules/FindSixense.cmake b/cmake/modules/FindSixense.cmake index 98b37d5410..64cc1e0ffb 100644 --- a/cmake/modules/FindSixense.cmake +++ b/cmake/modules/FindSixense.cmake @@ -51,7 +51,7 @@ select_library_configurations(SIXENSE) set(SIXENSE_REQUIREMENTS SIXENSE_INCLUDE_DIRS SIXENSE_LIBRARIES) if (WIN32) - list(APPEND SIXENSE_REQUIREMENTS SIXENSE_DEBUG_DLL_PATH SIXENSE_RELEASE_DLL_PATH SIXENSE_DEVICE_DLL_PATH) + list(APPEND SIXENSE_REQUIREMENTS SIXENSE_DEBUG_DLL_PATH SIXENSE_RELEASE_DLL_PATH) endif () set(SIXENSE_LIBRARIES "${SIXENSE_LIBRARY}") diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index c3ded6c587..41cee2f666 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -28,10 +28,10 @@ if (WIN32) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) endif() -#add_dependency_external_projects(Sixense) -#find_package(Sixense REQUIRED) -#target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) -#target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) +add_dependency_external_projects(Sixense) +find_package(Sixense REQUIRED) +target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) From 86e21c91ba3fa3e5057786aee1beec3ce296f6a0 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 18 Aug 2015 15:08:48 -0700 Subject: [PATCH 063/170] Case issues again --- cmake/externals/{sixense => Sixense}/CMakeLists.txt | 8 +++----- libraries/input-plugins/CMakeLists.txt | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) rename cmake/externals/{sixense => Sixense}/CMakeLists.txt (96%) diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/Sixense/CMakeLists.txt similarity index 96% rename from cmake/externals/sixense/CMakeLists.txt rename to cmake/externals/Sixense/CMakeLists.txt index d0778774cb..7991ccea1b 100644 --- a/cmake/externals/sixense/CMakeLists.txt +++ b/cmake/externals/Sixense/CMakeLists.txt @@ -1,10 +1,5 @@ -include(ExternalProject) -include(SelectLibraryConfigurations) - set(EXTERNAL_NAME Sixense) -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) - ExternalProject_Add( ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip @@ -15,8 +10,11 @@ ExternalProject_Add( LOG_DOWNLOAD 1 ) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) if (WIN32) diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index 41cee2f666..28af865c7a 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -32,6 +32,7 @@ add_dependency_external_projects(Sixense) find_package(Sixense REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) +message( ${SIXENSE_INCLUDE_DIRS}) # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) @@ -69,4 +70,4 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\") endif () endif () -endforeach() \ No newline at end of file +endforeach() From 7f09df1d0ba4877a6a9c7b032dab6bc25e8f389c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 18 Aug 2015 16:17:27 -0700 Subject: [PATCH 064/170] Fixing external --- cmake/externals/Sixense/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/externals/Sixense/CMakeLists.txt b/cmake/externals/Sixense/CMakeLists.txt index 7991ccea1b..46341bd37b 100644 --- a/cmake/externals/Sixense/CMakeLists.txt +++ b/cmake/externals/Sixense/CMakeLists.txt @@ -1,3 +1,6 @@ +include(ExternalProject) +include(SelectLibraryConfigurations) + set(EXTERNAL_NAME Sixense) ExternalProject_Add( From 940390ee46b1fa9663ba19282d271b076f401539 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 18 Aug 2015 19:31:05 -0700 Subject: [PATCH 065/170] make enable-avatar-collisions menu item work again --- interface/src/Menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 939d733a13..f074dc5ac7 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -258,7 +258,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, - avatar, SLOT(updateMotionBehavior())); + avatar, SLOT(updateMotionBehaviorFromMenu())); MenuWrapper* viewMenu = addMenu("View"); addActionToQMenuAndActionHash(viewMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); From aba7bebf6e5623be7c352ab79336703e51dd6f48 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 19 Aug 2015 09:44:51 -0700 Subject: [PATCH 066/170] Revert "Updating to the new version of the sixense library" --- CMakeLists.txt | 1 - cmake/externals/Sixense/CMakeLists.txt | 62 -------------------------- cmake/externals/sixense/CMakeLists.txt | 60 +++++++++++++++++++++++++ cmake/modules/FindSixense.cmake | 2 +- libraries/input-plugins/CMakeLists.txt | 11 +++-- 5 files changed, 66 insertions(+), 70 deletions(-) delete mode 100644 cmake/externals/Sixense/CMakeLists.txt create mode 100644 cmake/externals/sixense/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 41b88e4cea..38bcb42e26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,7 +187,6 @@ option(GET_OPENVR "Get OpenVR library automatically as external project" 1) option(GET_BOOSTCONFIG "Get Boost-config library automatically as external project" 1) option(GET_OGLPLUS "Get OGLplus library automatically as external project" 1) option(GET_GLEW "Get GLEW library automatically as external project" 1) -option(GET_SIXENSE "Get Sixense library automatically as external project" 1) option(USE_NSIGHT "Attempt to find the nSight libraries" 1) diff --git a/cmake/externals/Sixense/CMakeLists.txt b/cmake/externals/Sixense/CMakeLists.txt deleted file mode 100644 index 46341bd37b..0000000000 --- a/cmake/externals/Sixense/CMakeLists.txt +++ /dev/null @@ -1,62 +0,0 @@ -include(ExternalProject) -include(SelectLibraryConfigurations) - -set(EXTERNAL_NAME Sixense) - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip - URL_MD5 752a3901f334124e9cffc2ba4136ef7d - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD 1 -) - -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - -ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) - -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) - -if (WIN32) - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(ARCH_DIR "x64/VS2013") - set(ARCH_SUFFIX "_x64") - else() - set(ARCH_DIR "Win32/VS2013") - set(ARCH_SUFFIX "") - endif() - - set(LIB_DIR "${SOURCE_DIR}/lib/${ARCH_DIR}") - set(BIN_DIR "${SOURCE_DIR}/bin/${ARCH_DIR}") - set(SIXENSE_LIBRARY_RELEASE "${LIB_DIR}/release_dll/sixense${ARCH_SUFFIX}.lib" CACHE FILEPATH "Sixense release lib") - set(SIXENSE_LIBRARY_DEBUG "${LIB_DIR}/debug_dll/sixensed${ARCH_SUFFIX}.lib" CACHE FILEPATH "Sixense debug lib") - set(SIXENSE_RELEASE_DLL_PATH "${BIN_DIR}/release_dll" CACHE FILEPATH "Sixense release DLL path") - set(SIXENSE_DEBUG_DLL_PATH "${BIN_DIR}/debug_dll" CACHE FILEPATH "Sixense debug DLL path") - - # FIXME need to account for different architectures - add_paths_to_fixup_libs(${SIXENSE_DEBUG_DLL_PATH}) - add_paths_to_fixup_libs(${SIXENSE_RELEASE_DLL_PATH}) - -elseif(APPLE) - - set(ARCH_DIR "osx_x64") - set(ARCH_SUFFIX "_x64") - set(LIB_DIR "${SOURCE_DIR}/lib/${ARCH_DIR}") - - set(SIXENSE_LIBRARY_RELEASE "${LIB_DIR}/release_dll/libsixense${ARCH_SUFFIX}.dylib" CACHE FILEPATH "Sixense release lib") - set(SIXENSE_LIBRARY_DEBUG "${LIB_DIR}/debug_dll/libsixensed${ARCH_SUFFIX}.dylib" CACHE FILEPATH "Sixense debug lib") - -elseif(NOT ANDROID) - - set(ARCH_DIR "linux_x64") - set(ARCH_SUFFIX "_x64") - set(LIB_DIR "${SOURCE_DIR}/lib/${ARCH_DIR}") - - set(SIXENSE_LIBRARY_RELEASE "${LIB_DIR}/release/libsixense${ARCH_SUFFIX}.so" CACHE FILEPATH "Sixense release lib") - set(SIXENSE_LIBRARY_DEBUG "${LIB_DIR}/debug/libsixensed${ARCH_SUFFIX}.so" CACHE FILEPATH "Sixense debug lib") - -endif() - diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt new file mode 100644 index 0000000000..c80b492509 --- /dev/null +++ b/cmake/externals/sixense/CMakeLists.txt @@ -0,0 +1,60 @@ +include(ExternalProject) +include(SelectLibraryConfigurations) + +set(EXTERNAL_NAME Sixense) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ./SixenseSDK_062612.zip + URL_MD5 10cc8dc470d2ac1244a88cf04bc549cc + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +if (APPLE) + find_library(SIXENSE_LIBRARY_RELEASE lib/osx_x64/release_dll/libsixense_x64.dylib HINTS ${SIXENSE_SEARCH_DIRS}) + find_library(SIXENSE_LIBRARY_DEBUG lib/osx_x64/debug_dll/libsixensed_x64.dylib HINTS ${SIXENSE_SEARCH_DIRS}) +elseif (UNIX) + find_library(SIXENSE_LIBRARY_RELEASE lib/linux_x64/release/libsixense_x64.so HINTS ${SIXENSE_SEARCH_DIRS}) + # find_library(SIXENSE_LIBRARY_DEBUG lib/linux_x64/debug/libsixensed_x64.so HINTS ${SIXENSE_SEARCH_DIRS}) +elseif (WIN32) +endif () + + + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) + +if (WIN32) + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(ARCH_DIR "x64") + set(ARCH_SUFFIX "_x64") + else() + set(ARCH_DIR "Win32") + set(ARCH_SUFFIX "") + endif() + + # FIXME need to account for different architectures + set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${SOURCE_DIR}/lib/${ARCH_DIR}/release_dll/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win32) + +elseif(APPLE) + + # FIXME need to account for different architectures + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx32/libopenvr_api.dylib CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/osx32) + +elseif(NOT ANDROID) + + # FIXME need to account for different architectures + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux32/libopenvr_api.so CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/linux32) + +endif() + diff --git a/cmake/modules/FindSixense.cmake b/cmake/modules/FindSixense.cmake index 64cc1e0ffb..98b37d5410 100644 --- a/cmake/modules/FindSixense.cmake +++ b/cmake/modules/FindSixense.cmake @@ -51,7 +51,7 @@ select_library_configurations(SIXENSE) set(SIXENSE_REQUIREMENTS SIXENSE_INCLUDE_DIRS SIXENSE_LIBRARIES) if (WIN32) - list(APPEND SIXENSE_REQUIREMENTS SIXENSE_DEBUG_DLL_PATH SIXENSE_RELEASE_DLL_PATH) + list(APPEND SIXENSE_REQUIREMENTS SIXENSE_DEBUG_DLL_PATH SIXENSE_RELEASE_DLL_PATH SIXENSE_DEVICE_DLL_PATH) endif () set(SIXENSE_LIBRARIES "${SIXENSE_LIBRARY}") diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index 28af865c7a..c3ded6c587 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -28,11 +28,10 @@ if (WIN32) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) endif() -add_dependency_external_projects(Sixense) -find_package(Sixense REQUIRED) -target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) -message( ${SIXENSE_INCLUDE_DIRS}) +#add_dependency_external_projects(Sixense) +#find_package(Sixense REQUIRED) +#target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) +#target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) @@ -70,4 +69,4 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\") endif () endif () -endforeach() +endforeach() \ No newline at end of file From 0a8c1846e9539e5320cefde79d8416bbd0cb1124 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 19 Aug 2015 10:24:31 -0700 Subject: [PATCH 067/170] This assert causes dev builds to fail when starting interface. --- libraries/render-utils/src/OffscreenQmlSurface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp index 0dfd95a725..9923849aab 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.cpp +++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp @@ -239,7 +239,7 @@ private: return; } - Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _size); + //Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _size); //Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _textures._size); _renderControl->sync(); From 07ae00f20715b8a5c0ab814c31c50c9b49d5edca Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 19 Aug 2015 19:33:48 +0200 Subject: [PATCH 068/170] Close WebSocket properly after ScriptEngine finished running --- libraries/script-engine/src/WebSocketServerClass.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp index 7981d67de3..9fc060e61c 100644 --- a/libraries/script-engine/src/WebSocketServerClass.cpp +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -40,7 +40,9 @@ QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptE serverName = serverNameOption.toString(); } } - return engine->newQObject(new WebSocketServerClass(engine, serverName, port)); + auto webSocketServerClass = new WebSocketServerClass(engine, serverName, port); + connect(engine, SIGNAL(finished(const QString&)), webSocketServerClass, SLOT(deleteLater())); + return engine->newQObject(webSocketServerClass); } WebSocketServerClass::~WebSocketServerClass() { From ecd368fac35172a5de2c52259366c4934e4bdde5 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 19 Aug 2015 20:00:06 +0200 Subject: [PATCH 069/170] new style qt connectors --- libraries/script-engine/src/WebSocketServerClass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp index 9fc060e61c..dbe92510e4 100644 --- a/libraries/script-engine/src/WebSocketServerClass.cpp +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -41,7 +41,7 @@ QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptE } } auto webSocketServerClass = new WebSocketServerClass(engine, serverName, port); - connect(engine, SIGNAL(finished(const QString&)), webSocketServerClass, SLOT(deleteLater())); + connect(static_cast(engine), &ScriptEngine::finished, webSocketServerClass, &QObject::deleteLater); return engine->newQObject(webSocketServerClass); } From 92ae3e758c9a849c60e13132a18566ea062732e4 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 19 Aug 2015 12:28:07 -0700 Subject: [PATCH 070/170] restore DEFAULT_FAR_CLIP to 16km --- libraries/octree/src/ViewFrustum.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index 3e41b58bb6..fe40cfd53c 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -31,7 +31,7 @@ const float DEFAULT_KEYHOLE_RADIUS = 3.0f; const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f; const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f; const float DEFAULT_NEAR_CLIP = 0.08f; -const float DEFAULT_FAR_CLIP = (float)TREE_SCALE; +const float DEFAULT_FAR_CLIP = (float)HALF_TREE_SCALE; class ViewFrustum { public: From 9605294ea8b1b8fd2fd8062f427b2d9455de3955 Mon Sep 17 00:00:00 2001 From: Chris Collins Date: Wed, 19 Aug 2015 12:55:52 -0700 Subject: [PATCH 071/170] Removed HMDcontrol.js from default scripts Removed HMDcontrol.js from default scripts. Will put back when HMDcontrol is fixed --- examples/defaultScripts.js | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 922047b90c..c06ecd393d 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -17,5 +17,4 @@ Script.load("users.js"); Script.load("grab.js"); Script.load("directory.js"); Script.load("mouseLook.js"); -Script.load("hmdControls.js"); Script.load("dialTone.js"); From 846ca3b86b59b84190a3a72ee747eb3c3ce67910 Mon Sep 17 00:00:00 2001 From: Chris Collins Date: Wed, 19 Aug 2015 12:58:26 -0700 Subject: [PATCH 072/170] removed mouselook removed mouselook --- examples/defaultScripts.js | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index c06ecd393d..c602c36382 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -16,5 +16,4 @@ Script.load("notifications.js"); Script.load("users.js"); Script.load("grab.js"); Script.load("directory.js"); -Script.load("mouseLook.js"); Script.load("dialTone.js"); From 6860f3d126e4c7688016ca3536072034111dd82d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 19 Aug 2015 14:32:26 -0700 Subject: [PATCH 073/170] add readWithoutCopy for clarity in Packet read --- assignment-client/src/AssignmentClientMonitor.cpp | 2 +- assignment-client/src/audio/AudioMixerClientData.cpp | 2 +- .../src/avatars/AvatarMixerClientData.cpp | 2 +- libraries/audio/src/InboundAudioStream.cpp | 4 ++-- libraries/avatars/src/AvatarHashMap.cpp | 10 +++++----- libraries/entities/src/EntityTree.cpp | 2 +- libraries/networking/src/LimitedNodeList.cpp | 2 +- libraries/networking/src/NodeList.cpp | 2 +- libraries/networking/src/udt/Packet.cpp | 7 +++++++ libraries/networking/src/udt/Packet.h | 3 ++- 10 files changed, 22 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 72765dd0df..ddea6cc702 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -203,7 +203,7 @@ void AssignmentClientMonitor::checkSpares() { void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer packet) { // read out the sender ID - QUuid senderID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID)); + QUuid senderID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); auto nodeList = DependencyManager::get(); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 10a5992fd8..7e822951d8 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -90,7 +90,7 @@ int AudioMixerClientData::parseData(NLPacket& packet) { // grab the stream identifier for this injected audio packet.seek(sizeof(quint16)); - QUuid streamIdentifier = QUuid::fromRfc4122(packet.read(NUM_BYTES_RFC4122_UUID)); + QUuid streamIdentifier = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); bool isStereo; packet.readPrimitive(&isStereo); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index fec51f76cb..65660b178e 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -15,7 +15,7 @@ int AvatarMixerClientData::parseData(NLPacket& packet) { // compute the offset to the data payload - return _avatar.parseDataFromBuffer(packet.read(packet.bytesLeftToRead())); + return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); } bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() { diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 8888c9bae5..d552db1735 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -112,7 +112,7 @@ int InboundAudioStream::parseData(NLPacket& packet) { // parse the info after the seq number and before the audio data (the stream properties) int prePropertyPosition = packet.pos(); - int propertyBytes = parseStreamProperties(packet.getType(), packet.read(packet.bytesLeftToRead()), networkSamples); + int propertyBytes = parseStreamProperties(packet.getType(), packet.readWithoutCopy(packet.bytesLeftToRead()), networkSamples); packet.seek(prePropertyPosition + propertyBytes); // handle this packet based on its arrival status. @@ -131,7 +131,7 @@ int InboundAudioStream::parseData(NLPacket& packet) { if (packet.getType() == PacketType::SilentAudioFrame) { writeDroppableSilentSamples(networkSamples); } else { - parseAudioData(packet.getType(), packet.read(packet.bytesLeftToRead()), networkSamples); + parseAudioData(packet.getType(), packet.readWithoutCopy(packet.bytesLeftToRead()), networkSamples); } break; } diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 5c65f89990..d2ec5d68f0 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -53,11 +53,11 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer packet, Sha // enumerate over all of the avatars in this packet // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (packet->bytesLeftToRead()) { - QUuid sessionUUID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID)); + QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); int positionBeforeRead = packet->pos(); - QByteArray byteArray = packet->read(packet->bytesLeftToRead()); + QByteArray byteArray = packet->readWithoutCopy(packet->bytesLeftToRead()); if (sessionUUID != _lastOwnerSessionUUID) { AvatarSharedPointer avatar = _avatarHash.value(sessionUUID); @@ -114,14 +114,14 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer packet, } void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer packet, SharedNodePointer sendingNode) { - QUuid sessionUUID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID)); + QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); AvatarSharedPointer avatar = _avatarHash.value(sessionUUID); if (!avatar) { avatar = addAvatar(sessionUUID, sendingNode); } - QByteArray billboard = packet->read(packet->bytesLeftToRead()); + QByteArray billboard = packet->readWithoutCopy(packet->bytesLeftToRead()); if (avatar->getBillboard() != billboard) { avatar->setBillboard(billboard); } @@ -129,7 +129,7 @@ void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer packet void AvatarHashMap::processKillAvatar(QSharedPointer packet, SharedNodePointer sendingNode) { // read the node id - QUuid sessionUUID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID)); + QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); removeAvatar(sessionUUID); } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ba2691aa3b..ba0e3f495f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -873,7 +873,7 @@ int EntityTree::processEraseMessage(NLPacket& packet, const SharedNodePointer& s break; // bail to prevent buffer overflow } - QUuid entityID = QUuid::fromRfc4122(packet.read(NUM_BYTES_RFC4122_UUID)); + QUuid entityID = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); EntityItemID entityItemID(entityID); entityItemIDsToDelete << entityItemID; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index fdb3461c1f..48febf39bd 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -397,7 +397,7 @@ void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { void LimitedNodeList::processKillNode(NLPacket& packet) { // read the node id - QUuid nodeUUID = QUuid::fromRfc4122(packet.read(NUM_BYTES_RFC4122_UUID)); + QUuid nodeUUID = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); // kill the node with this UUID, if it exists killNodeWithUUID(nodeUUID); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 563b7d7080..6616707579 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -463,7 +463,7 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer return; } // read in the connection token from the packet, then send domain-server checkin - _domainHandler.setConnectionToken(QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID))); + _domainHandler.setConnectionToken(QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID))); sendDomainServerCheckIn(); } diff --git a/libraries/networking/src/udt/Packet.cpp b/libraries/networking/src/udt/Packet.cpp index 13f8a39e26..81747749be 100644 --- a/libraries/networking/src/udt/Packet.cpp +++ b/libraries/networking/src/udt/Packet.cpp @@ -213,6 +213,13 @@ void Packet::writeSequenceNumber(SequenceNumber seqNum) { } QByteArray Packet::read(qint64 maxSize) { + qint64 sizeToRead = std::min(size() - pos(), maxSize); + QByteArray data { getPayload() + pos(), (int) sizeToRead }; + seek(pos() + sizeToRead); + return data; +} + +QByteArray Packet::readWithoutCopy(qint64 maxSize) { qint64 sizeToRead = std::min(size() - pos(), maxSize); QByteArray data { QByteArray::fromRawData(getPayload() + pos(), sizeToRead) }; seek(pos() + sizeToRead); diff --git a/libraries/networking/src/udt/Packet.h b/libraries/networking/src/udt/Packet.h index 91b5974e09..569ff4b9a4 100644 --- a/libraries/networking/src/udt/Packet.h +++ b/libraries/networking/src/udt/Packet.h @@ -81,7 +81,8 @@ public: using QIODevice::read; QByteArray read(qint64 maxSize); - + QByteArray readWithoutCopy(qint64 maxSize); // this can only be used if packet will stay in scope + template qint64 peekPrimitive(T* data); template qint64 readPrimitive(T* data); template qint64 writePrimitive(const T& data); From c8fbb64bbf11ff53ba3172e60aa47d3edd23c2a1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 19 Aug 2015 14:36:16 -0700 Subject: [PATCH 074/170] fix AvatarHashMap read due to COW --- libraries/avatars/src/AvatarHashMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d2ec5d68f0..4378e818ec 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -121,7 +121,7 @@ void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer packet avatar = addAvatar(sessionUUID, sendingNode); } - QByteArray billboard = packet->readWithoutCopy(packet->bytesLeftToRead()); + QByteArray billboard = packet->read(packet->bytesLeftToRead()); if (avatar->getBillboard() != billboard) { avatar->setBillboard(billboard); } From 010a3d8220a11783f155028a41ce9fc8b46321ca Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 19 Aug 2015 17:12:03 -0700 Subject: [PATCH 075/170] fix create in negative space bug --- examples/edit.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index f0d782cd4a..deda035d5e 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1036,6 +1036,7 @@ function handeMenuEvent(menuItem) { // This function tries to find a reasonable position to place a new entity based on the camera // position. If a reasonable position within the world bounds can't be found, `null` will // be returned. The returned position will also take into account grid snapping settings. +// FIXME - technically we should guard against very large positions too function getPositionToCreateEntity() { var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE; var direction = Quat.getFront(Camera.orientation); @@ -1055,9 +1056,9 @@ function getPositionToCreateEntity() { return null; } - placementPosition.x = Math.max(0, placementPosition.x); - placementPosition.y = Math.max(0, placementPosition.y); - placementPosition.z = Math.max(0, placementPosition.z); + placementPosition.x = Math.max(-HALF_TREE_SCALE, placementPosition.x); + placementPosition.y = Math.max(-HALF_TREE_SCALE, placementPosition.y); + placementPosition.z = Math.max(-HALF_TREE_SCALE, placementPosition.z); return placementPosition; } From 2056f588e3575e0dae35b66c288f0147fe7bbe39 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 19 Aug 2015 18:03:16 -0700 Subject: [PATCH 076/170] if environment variable HIFI_MEMORY_DEBUGGING is defined when cmake is run, enable -fsanitize=address on linux --- assignment-client/CMakeLists.txt | 10 +++++++++- domain-server/CMakeLists.txt | 8 ++++++++ interface/CMakeLists.txt | 8 ++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index edd68e12bf..7e3b2e6af9 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -18,4 +18,12 @@ if (UNIX) endif (UNIX) include_application_version() -copy_dlls_beside_windows_executable() \ No newline at end of file +copy_dlls_beside_windows_executable() + +if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + if (UNIX) + MESSAGE("-- assignment-client memory debugging is enabled.") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + endif (UNIX) +endif () diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index e4fa1d874d..0d56e34cf2 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -33,3 +33,11 @@ target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) include_application_version() copy_dlls_beside_windows_executable() + +if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + if (UNIX) + MESSAGE("-- domain-server memory debugging is enabled.") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + endif (UNIX) +endif () diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index acafafa006..16d8bacd23 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -206,3 +206,11 @@ else (APPLE) endif (APPLE) copy_dlls_beside_windows_executable() + +if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + if (UNIX) + MESSAGE("-- interface memory debugging is enabled.") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + endif (UNIX) +endif () From 2738f65c10b624a7e49d3edc7d935b8df1ba8d96 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 19 Aug 2015 18:23:11 -0700 Subject: [PATCH 077/170] have domain-server report non-skewed uptime --- domain-server/resources/web/index.shtml | 2 +- domain-server/resources/web/js/tables.js | 4 ++-- domain-server/src/DomainServer.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/domain-server/resources/web/index.shtml b/domain-server/resources/web/index.shtml index 0f720ebe79..ea941a73fd 100644 --- a/domain-server/resources/web/index.shtml +++ b/domain-server/resources/web/index.shtml @@ -31,7 +31,7 @@ <%- node.username %> <%- node.public.ip %>:<%- node.public.port %> <%- node.local.ip %>:<%- node.local.port %> - <%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %> + <%- node.uptime %> <%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %> diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index 0b29d4e6c9..b2e0679d8b 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -9,9 +9,9 @@ $(document).ready(function(){ json.nodes.sort(function(a, b){ if (a.type === b.type) { - if (a.wake_timestamp < b.wake_timestamp) { + if (a.uptime > b.uptime) { return 1; - } else if (a.wake_timestamp > b.wake_timestamp) { + } else if (a.uptime < b.uptime) { return -1; } else { return 0; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 071626ef1e..369cb3b761 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1480,7 +1480,7 @@ const char JSON_KEY_PUBLIC_SOCKET[] = "public"; const char JSON_KEY_LOCAL_SOCKET[] = "local"; const char JSON_KEY_POOL[] = "pool"; const char JSON_KEY_PENDING_CREDITS[] = "pending_credits"; -const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp"; +const char JSON_KEY_UPTIME[] = "uptime"; const char JSON_KEY_USERNAME[] = "username"; const char JSON_KEY_VERSION[] = "version"; QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { @@ -1502,7 +1502,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket()); // add the node uptime in our list - nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp()); + nodeJson[JSON_KEY_UPTIME] = QString::number((QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0); // if the node has pool information, add it DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); From b386a95d0852a4284d414e26ac6bbd56d95fed44 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 19 Aug 2015 18:31:42 -0700 Subject: [PATCH 078/170] fix sort for node uptime --- domain-server/resources/web/js/tables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index b2e0679d8b..09f85a7047 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -9,9 +9,9 @@ $(document).ready(function(){ json.nodes.sort(function(a, b){ if (a.type === b.type) { - if (a.uptime > b.uptime) { + if (a.uptime < b.uptime) { return 1; - } else if (a.uptime < b.uptime) { + } else if (a.uptime > b.uptime) { return -1; } else { return 0; From 6a7f367e5fecc2e37000818e1965be57789323f1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 19 Aug 2015 21:11:48 -0700 Subject: [PATCH 079/170] fix throttled rendering for the 2D display plugin --- interface/src/Application.cpp | 27 ++++++++- interface/src/Application.h | 3 + interface/src/GLCanvas.cpp | 59 ++----------------- interface/src/GLCanvas.h | 10 ---- interface/src/Menu.h | 2 +- .../Basic2DWindowOpenGLDisplayPlugin.cpp | 21 +++++++ .../Basic2DWindowOpenGLDisplayPlugin.h | 6 ++ .../src/display-plugins/OpenGLDisplayPlugin.h | 4 +- .../plugins/src/plugins/PluginContainer.h | 1 + 9 files changed, 64 insertions(+), 69 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4dd61b7982..15f4a48405 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -175,8 +175,6 @@ public: using namespace std; // Starfield information -static uint8_t THROTTLED_IDLE_TIMER_DELAY = 10; - const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB static QTimer* locationUpdateTimer = NULL; @@ -737,6 +735,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _keyboardFocusHighlight->setVisible(false); } }); + + connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); } void Application::aboutToQuit() { @@ -2108,8 +2108,11 @@ void Application::idle() { // Once rendering is off on another thread we should be able to have Application::idle run at start(0) in // perpetuity and not expect events to get backed up. + bool isThrottled = getActiveDisplayPlugin()->isThrottled(); + static const int THROTTLED_IDLE_TIMER_DELAY = MSECS_PER_SECOND / 15; static const int IDLE_TIMER_DELAY_MS = 2; - int desiredInterval = getActiveDisplayPlugin()->isThrottled() ? THROTTLED_IDLE_TIMER_DELAY : IDLE_TIMER_DELAY_MS; + int desiredInterval = isThrottled ? THROTTLED_IDLE_TIMER_DELAY : IDLE_TIMER_DELAY_MS; + //qDebug() << "isThrottled:" << isThrottled << "desiredInterval:" << desiredInterval; if (idleTimer->interval() != desiredInterval) { idleTimer->start(desiredInterval); @@ -4612,6 +4615,24 @@ void Application::checkSkeleton() { } } +bool Application::isForeground() { + return _isForeground && !getWindow()->isMinimized(); +} + +void Application::activeChanged(Qt::ApplicationState state) { + switch (state) { + case Qt::ApplicationActive: + _isForeground = true; + break; + + case Qt::ApplicationSuspended: + case Qt::ApplicationHidden: + case Qt::ApplicationInactive: + default: + _isForeground = false; + break; + } +} void Application::showFriendsWindow() { const QString FRIENDS_WINDOW_TITLE = "Add/Remove Friends"; const QString FRIENDS_WINDOW_URL = "https://metaverse.highfidelity.com/user/friends"; diff --git a/interface/src/Application.h b/interface/src/Application.h index 4819bd08a4..a1765109ce 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -291,6 +291,7 @@ public: virtual void unsetFullscreen(const QScreen* avoid) override; virtual void showDisplayPluginsTools() override; virtual QGLWidget* getPrimarySurface() override; + virtual bool isForeground() override; void setActiveDisplayPlugin(const QString& pluginName); @@ -476,6 +477,7 @@ private slots: void faceTrackerMuteToggled(); void setCursorVisible(bool visible); + void activeChanged(Qt::ApplicationState state); private: void resetCameras(Camera& camera, const glm::uvec2& size); @@ -688,6 +690,7 @@ private: SimpleMovingAverage _simsPerSecond{10}; int _simsPerSecondReport = 0; quint64 _lastSimsPerSecondUpdate = 0; + bool _isForeground = true; // starts out assumed to be in foreground }; #endif // hifi_Application_h diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 0d0d335b67..bfa2dacf3a 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -18,8 +18,6 @@ #include "MainWindow.h" -const int MSECS_PER_FRAME_WHEN_THROTTLED = 66; - static QGLFormat& getDesiredGLFormat() { // Specify an OpenGL 3.3 format using the Core profile. // That is, no old-school fixed pipeline functionality @@ -35,10 +33,7 @@ static QGLFormat& getDesiredGLFormat() { return glFormat; } -GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()), - _throttleRendering(false), - _idleRenderInterval(MSECS_PER_FRAME_WHEN_THROTTLED) -{ +GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()) { #ifdef Q_OS_LINUX // Cause GLCanvas::eventFilter to be called. // It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux. @@ -46,15 +41,6 @@ GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()), #endif } -void GLCanvas::stopFrameTimer() { - _frameTimer.stop(); -} - -bool GLCanvas::isThrottleRendering() const { - return (_throttleRendering - || (Application::getInstance()->getWindow()->isMinimized() && Application::getInstance()->isThrottleFPSEnabled())); -} - int GLCanvas::getDeviceWidth() const { return width() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); } @@ -66,17 +52,17 @@ int GLCanvas::getDeviceHeight() const { void GLCanvas::initializeGL() { setAttribute(Qt::WA_AcceptTouchEvents); setAcceptDrops(true); - connect(Application::getInstance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(activeChanged(Qt::ApplicationState))); - connect(&_frameTimer, SIGNAL(timeout()), this, SLOT(throttleRender())); - // Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate. setAutoBufferSwap(false); } void GLCanvas::paintGL() { PROFILE_RANGE(__FUNCTION__); - if (!_throttleRendering && - (!Application::getInstance()->getWindow()->isMinimized() || !Application::getInstance()->isThrottleFPSEnabled())) { + + // FIXME - I'm not sure why this still remains, it appears as if this GLCanvas gets a single paintGL call near + // the beginning of the application starting up. I'm not sure if we really need to call Application::paintGL() + // in this case, since the display plugins eventually handle all the painting + if ((!Application::getInstance()->getWindow()->isMinimized() || !Application::getInstance()->isThrottleFPSEnabled())) { Application::getInstance()->paintGL(); } } @@ -85,39 +71,6 @@ void GLCanvas::resizeGL(int width, int height) { Application::getInstance()->resizeGL(); } -void GLCanvas::activeChanged(Qt::ApplicationState state) { - switch (state) { - case Qt::ApplicationActive: - // If we're active, stop the frame timer and the throttle. - _frameTimer.stop(); - _throttleRendering = false; - break; - - case Qt::ApplicationSuspended: - case Qt::ApplicationHidden: - // If we're hidden or are about to suspend, don't render anything. - _throttleRendering = false; - _frameTimer.stop(); - break; - - default: - // Otherwise, throttle. - if (!_throttleRendering && !Application::getInstance()->isAboutToQuit() - && Application::getInstance()->isThrottleFPSEnabled()) { - _frameTimer.start(_idleRenderInterval); - _throttleRendering = true; - } - break; - } -} - -void GLCanvas::throttleRender() { - _frameTimer.start(_idleRenderInterval); - if (!Application::getInstance()->getWindow()->isMinimized()) { - Application::getInstance()->paintGL(); - } -} - int updateTime = 0; bool GLCanvas::event(QEvent* event) { switch (event->type()) { diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index bcaa9577d1..73c5b5e8bf 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -23,28 +23,18 @@ class GLCanvas : public QGLWidget { public: GLCanvas(); - void stopFrameTimer(); - - bool isThrottleRendering() const; - int getDeviceWidth() const; int getDeviceHeight() const; QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } protected: - QTimer _frameTimer; - bool _throttleRendering; - int _idleRenderInterval; - virtual void initializeGL(); virtual void paintGL(); virtual void resizeGL(int width, int height); virtual bool event(QEvent* event); private slots: - void activeChanged(Qt::ApplicationState state); - void throttleRender(); bool eventFilter(QObject*, QEvent* event); }; diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b0a78fdd83..ca46b80f92 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -283,7 +283,7 @@ namespace MenuOption { const QString TestPing = "Test Ping"; const QString ThirdPerson = "Third Person"; const QString ThreePointCalibration = "3 Point Calibration"; - const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; + const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp const QString ToolWindow = "Tool Window"; const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index ce6467cd55..e478223281 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -34,3 +34,24 @@ void Basic2DWindowOpenGLDisplayPlugin::deactivate() { // container->removeMenu(MENU_PATH); MainWindowOpenGLDisplayPlugin::deactivate(); } + +int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval(bool isThrottled) const { + static const int THROTTLED_PAINT_TIMER_DELAY = MSECS_PER_SECOND / 15; + static const int PAINT_TIMER_DELAY_MS = 1; + + return isThrottled ? THROTTLED_PAINT_TIMER_DELAY : PAINT_TIMER_DELAY_MS; +} + +bool Basic2DWindowOpenGLDisplayPlugin::isThrottled() const { + static const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Menu.h + + bool shouldThrottle = (!CONTAINER->isForeground() && CONTAINER->isOptionChecked(ThrottleFPSIfNotFocus)); + + if (_isThrottled != shouldThrottle) { + int desiredInterval = getDesiredInterval(shouldThrottle); + _timer.start(desiredInterval); + _isThrottled = shouldThrottle; + } + + return shouldThrottle; +} \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index d19326f007..612d15377a 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -18,6 +18,12 @@ public: virtual const QString & getName() const override; + virtual bool isThrottled() const override; + +protected: + int getDesiredInterval(bool isThrottled) const; + mutable bool _isThrottled = false; + private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 9986610a50..9b6b92d8d4 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -37,8 +37,8 @@ protected: virtual void doneCurrent() = 0; virtual void swapBuffers() = 0; - QTimer _timer; - ProgramPtr _program; + mutable QTimer _timer; + ProgramPtr _program; ShapeWrapperPtr _plane; }; diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index 85c5c814de..7f6346a181 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -26,4 +26,5 @@ public: virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) = 0; virtual void showDisplayPluginsTools() = 0; virtual QGLWidget* getPrimarySurface() = 0; + virtual bool isForeground() = 0; }; From 002aea333352b58bf6c107363066a26e486ddb40 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 20 Aug 2015 00:30:44 -0700 Subject: [PATCH 080/170] Add fullscreen menu item to 2D display plugin --- .../Basic2DWindowOpenGLDisplayPlugin.cpp | 27 ++++++++++++------- .../Basic2DWindowOpenGLDisplayPlugin.h | 3 +++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index e478223281..7667fa1a29 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -9,12 +9,11 @@ #include #include +#include const QString Basic2DWindowOpenGLDisplayPlugin::NAME("2D Display"); -const QString MENU_PARENT = "View"; -const QString MENU_NAME = "Display Options"; -const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; +const QString MENU_PATH = "Display"; const QString FULLSCREEN = "Fullscreen"; const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { @@ -22,16 +21,19 @@ const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { } void Basic2DWindowOpenGLDisplayPlugin::activate() { -// container->addMenu(MENU_PATH); -// container->addMenuItem(MENU_PATH, FULLSCREEN, -// [this] (bool clicked) { this->setFullscreen(clicked); }, -// true, false); + CONTAINER->addMenu(MENU_PATH); + CONTAINER->addMenuItem(MENU_PATH, FULLSCREEN, + [this](bool clicked) { + if (clicked) { + CONTAINER->setFullscreen(getFullscreenTarget()); + } else { + CONTAINER->unsetFullscreen(); + } + }, true, false); MainWindowOpenGLDisplayPlugin::activate(); } void Basic2DWindowOpenGLDisplayPlugin::deactivate() { -// container->removeMenuItem(MENU_NAME, FULLSCREEN); -// container->removeMenu(MENU_PATH); MainWindowOpenGLDisplayPlugin::deactivate(); } @@ -54,4 +56,9 @@ bool Basic2DWindowOpenGLDisplayPlugin::isThrottled() const { } return shouldThrottle; -} \ No newline at end of file +} + +// FIXME target the screen the window is currently on +QScreen* Basic2DWindowOpenGLDisplayPlugin::getFullscreenTarget() { + return qApp->primaryScreen(); +} diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 612d15377a..477e214f4e 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -9,6 +9,7 @@ #include "MainWindowOpenGLDisplayPlugin.h" +class QScreen; class Basic2DWindowOpenGLDisplayPlugin : public MainWindowOpenGLDisplayPlugin { Q_OBJECT @@ -26,4 +27,6 @@ protected: private: static const QString NAME; + QScreen* getFullscreenTarget(); + int _fullscreenTarget{ -1 }; }; From 9faf45219604941cf3ae9ee48f4107094d77b254 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 20 Aug 2015 00:16:50 -0700 Subject: [PATCH 081/170] Make sure that items added via plugins can be removed as well --- interface/src/Application.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 15f4a48405..5f8b17f178 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4746,6 +4746,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti } static QVector> _currentDisplayPluginActions; +static bool _activatingDisplayPlugin{false}; void Application::updateDisplayMode() { auto menu = Menu::getInstance(); @@ -4792,7 +4793,9 @@ void Application::updateDisplayMode() { if (newDisplayPlugin) { _offscreenContext->makeCurrent(); + _activatingDisplayPlugin = true; newDisplayPlugin->activate(); + _activatingDisplayPlugin = false; _offscreenContext->makeCurrent(); offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); _offscreenContext->makeCurrent(); @@ -4909,14 +4912,17 @@ void Application::removeMenu(const QString& menuName) { void Application::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { auto menu = Menu::getInstance(); MenuWrapper* parentItem = menu->getMenu(path); - QAction* action = parentItem->addAction(name); + QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); connect(action, &QAction::triggered, [=] { onClicked(action->isChecked()); }); action->setCheckable(checkable); action->setChecked(checked); - _currentDisplayPluginActions.push_back({ path, name }); - _currentInputPluginActions.push_back({ path, name }); + if (_activatingDisplayPlugin) { + _currentDisplayPluginActions.push_back({ path, name }); + } else { + _currentInputPluginActions.push_back({ path, name }); + } } void Application::removeMenuItem(const QString& menuName, const QString& menuItem) { From 2b26f302fd0353c668230c632b950057ecdf741d Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Thu, 20 Aug 2015 08:01:33 -0600 Subject: [PATCH 082/170] Better handling of socket binding --- .../embedded-webserver/src/HTTPManager.cpp | 30 +++++++++++++++---- .../embedded-webserver/src/HTTPManager.h | 11 ++++++- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 19443e01da..0d91672904 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include @@ -19,16 +20,18 @@ #include "EmbeddedWebserverLogging.h" #include "HTTPManager.h" +const int SOCKET_ERROR_EXIT_CODE = 2; + HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : QTcpServer(parent), _documentRoot(documentRoot), - _requestHandler(requestHandler) + _requestHandler(requestHandler), + _port(port) { - // start listening on the passed port - if (!listen(QHostAddress("0.0.0.0"), port)) { - qCDebug(embeddedwebserver) << "Failed to open HTTP server socket:" << errorString(); - return; - } + bindSocket(); + _isListeningTimer = new QTimer(this); + connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening); + _isListeningTimer->start(10000); } void HTTPManager::incomingConnection(qintptr socketDescriptor) { @@ -157,3 +160,18 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) { return _requestHandler && _requestHandler->handleHTTPRequest(connection, url); } + +void HTTPManager::isTcpServerListening() { + if (!isListening()) { + qCWarning(embeddedwebserver) << "Socket on port " << QString::number(_port) << " is no longer listening"; + bindSocket(); + }} + +bool HTTPManager::bindSocket() { + qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); + if (!listen(QHostAddress::Any, _port)) { + qCWarning(embeddedwebserver) << "Failed to open HTTP server socket:" << errorString() << " can't continue"; + QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); + } + return true; +} \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 83c4103c15..6375b10205 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -17,6 +17,7 @@ #define hifi_HTTPManager_h #include +#include class HTTPConnection; class HTTPSConnection; @@ -35,14 +36,22 @@ public: HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); + +private slots: + void isTcpServerListening(); + +private: + bool bindSocket(); protected: /// Accepts all pending connections virtual void incomingConnection(qintptr socketDescriptor); virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url); -protected: + QString _documentRoot; HTTPRequestHandler* _requestHandler; + QTimer* _isListeningTimer; + const quint16 _port; }; #endif // hifi_HTTPManager_h From f87f3eb03319af5b6fedff39b09a2b4f00403866 Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Thu, 20 Aug 2015 08:02:45 -0600 Subject: [PATCH 083/170] Typo --- libraries/embedded-webserver/src/HTTPManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 0d91672904..c0d49e8638 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -165,7 +165,8 @@ void HTTPManager::isTcpServerListening() { if (!isListening()) { qCWarning(embeddedwebserver) << "Socket on port " << QString::number(_port) << " is no longer listening"; bindSocket(); - }} + } +} bool HTTPManager::bindSocket() { qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); From 2c23dab2e74b5333603be7ba224445135e683833 Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Thu, 20 Aug 2015 08:03:37 -0600 Subject: [PATCH 084/170] More specific logging --- libraries/embedded-webserver/src/HTTPManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index c0d49e8638..6e953598de 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -171,7 +171,7 @@ void HTTPManager::isTcpServerListening() { bool HTTPManager::bindSocket() { qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); if (!listen(QHostAddress::Any, _port)) { - qCWarning(embeddedwebserver) << "Failed to open HTTP server socket:" << errorString() << " can't continue"; + qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); } return true; From 129761c002781c0c48341dda89d78142af765279 Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Thu, 20 Aug 2015 08:10:19 -0600 Subject: [PATCH 085/170] making qtimer interval a const --- libraries/embedded-webserver/src/HTTPManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 6e953598de..72436fc55e 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -21,6 +21,7 @@ #include "HTTPManager.h" const int SOCKET_ERROR_EXIT_CODE = 2; +const int SOCKET_CHECK_INTERVAL_IN_MS = 30000; HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : QTcpServer(parent), @@ -31,7 +32,7 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH bindSocket(); _isListeningTimer = new QTimer(this); connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening); - _isListeningTimer->start(10000); + _isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS); } void HTTPManager::incomingConnection(qintptr socketDescriptor) { From cd050b2e60ddd787058db61443a3ae7eca71a562 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 20 Aug 2015 09:53:58 -0700 Subject: [PATCH 086/170] don't show hmd tools for direct mod HMDs --- interface/src/Application.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5f8b17f178..a28435e258 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4803,10 +4803,17 @@ void Application::updateDisplayMode() { oldDisplayPlugin = _displayPlugin; _displayPlugin = newDisplayPlugin; - + + // If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed + // Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1 + bool newPluginWantsHMDTools = newDisplayPlugin ? + (newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false; + bool oldPluginWantedHMDTools = oldDisplayPlugin ? + (oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false; + // Only show the hmd tools after the correct plugin has // been activated so that it's UI is setup correctly - if (newDisplayPlugin->isHmd()) { + if (newPluginWantsHMDTools) { showDisplayPluginsTools(); } @@ -4815,7 +4822,7 @@ void Application::updateDisplayMode() { _offscreenContext->makeCurrent(); // if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools - if (oldDisplayPlugin->isHmd() && !newDisplayPlugin->isHmd()) { + if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) { DependencyManager::get()->hmdTools(false); } } From c25082d86f45c5562750ddd81e6afb77f159b903 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 20 Aug 2015 10:14:16 -0700 Subject: [PATCH 087/170] use -fsanitize=address in all code rather than just the top-level links --- CMakeLists.txt | 6 ++++++ assignment-client/CMakeLists.txt | 11 +++-------- cmake/macros/MemoryDebugger.cmake | 21 +++++++++++++++++++++ domain-server/CMakeLists.txt | 10 ++-------- gvr-interface/CMakeLists.txt | 4 +++- ice-server/CMakeLists.txt | 4 +++- interface/CMakeLists.txt | 10 ++-------- libraries/animation/CMakeLists.txt | 4 +++- libraries/audio-client/CMakeLists.txt | 4 +++- libraries/audio/CMakeLists.txt | 2 ++ libraries/auto-updater/CMakeLists.txt | 3 +++ libraries/avatars/CMakeLists.txt | 2 ++ libraries/display-plugins/CMakeLists.txt | 4 +++- libraries/embedded-webserver/CMakeLists.txt | 4 +++- libraries/entities-renderer/CMakeLists.txt | 2 ++ libraries/entities/CMakeLists.txt | 2 ++ libraries/environment/CMakeLists.txt | 4 +++- libraries/fbx/CMakeLists.txt | 4 +++- libraries/gpu/CMakeLists.txt | 2 ++ libraries/input-plugins/CMakeLists.txt | 4 +++- libraries/model/CMakeLists.txt | 4 +++- libraries/networking/CMakeLists.txt | 4 +++- libraries/octree/CMakeLists.txt | 2 ++ libraries/physics/CMakeLists.txt | 2 ++ libraries/plugins/CMakeLists.txt | 5 +++-- libraries/render-utils/CMakeLists.txt | 2 ++ libraries/render/CMakeLists.txt | 4 +++- libraries/script-engine/CMakeLists.txt | 2 ++ libraries/shared/CMakeLists.txt | 2 ++ libraries/ui/CMakeLists.txt | 5 +++-- tests/CMakeLists.txt | 2 ++ tests/animation/CMakeLists.txt | 2 ++ tests/audio/CMakeLists.txt | 4 +++- tests/entities/CMakeLists.txt | 4 +++- tests/jitter/CMakeLists.txt | 4 +++- tests/networking/CMakeLists.txt | 4 +++- tests/octree/CMakeLists.txt | 4 +++- tests/physics/CMakeLists.txt | 2 ++ tests/render-utils/CMakeLists.txt | 3 +++ tests/shaders/CMakeLists.txt | 3 +++ tests/shared/CMakeLists.txt | 4 +++- tests/ui/CMakeLists.txt | 4 +++- tools/CMakeLists.txt | 3 ++- tools/mtc/CMakeLists.txt | 4 +++- tools/scribe/CMakeLists.txt | 5 ++++- tools/vhacd-util/CMakeLists.txt | 2 ++ 46 files changed, 143 insertions(+), 50 deletions(-) create mode 100644 cmake/macros/MemoryDebugger.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 38bcb42e26..bc4c938f08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,3 +213,9 @@ endif () if (ANDROID OR DESKTOP_GVR) add_subdirectory(gvr-interface) endif () + +if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + if (UNIX) + MESSAGE("-- Memory debugging is enabled") + endif (UNIX) +endif () diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 7e3b2e6af9..315eeb6b83 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -18,12 +18,7 @@ if (UNIX) endif (UNIX) include_application_version() -copy_dlls_beside_windows_executable() -if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) - if (UNIX) - MESSAGE("-- assignment-client memory debugging is enabled.") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") - endif (UNIX) -endif () +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake new file mode 100644 index 0000000000..cb907efa96 --- /dev/null +++ b/cmake/macros/MemoryDebugger.cmake @@ -0,0 +1,21 @@ +# +# MemoryDebugger.cmake +# +# Copyright 2015 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(SETUP_MEMORY_DEBUGGER) +if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + SET( HIFI_MEMORY_DEBUGGING true ) +endif () + +if (HIFI_MEMORY_DEBUGGING) + if (UNIX) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") + endif (UNIX) +endif () +endmacro(SETUP_MEMORY_DEBUGGER) diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 0d56e34cf2..d2f30b6c25 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME domain-server) +setup_memory_debugger() + if (UPPER_CMAKE_BUILD_TYPE MATCHES DEBUG AND NOT WIN32) set(_SHOULD_SYMLINK_RESOURCES TRUE) else () @@ -33,11 +35,3 @@ target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) include_application_version() copy_dlls_beside_windows_executable() - -if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) - if (UNIX) - MESSAGE("-- domain-server memory debugging is enabled.") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") - endif (UNIX) -endif () diff --git a/gvr-interface/CMakeLists.txt b/gvr-interface/CMakeLists.txt index a986fcae0d..c4880a80b6 100644 --- a/gvr-interface/CMakeLists.txt +++ b/gvr-interface/CMakeLists.txt @@ -88,4 +88,6 @@ if (ANDROID) endif (ANDROID) -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index 13d89fc4a2..d62192bcec 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -1,9 +1,11 @@ set(TARGET_NAME ice-server) +setup_memory_debugger() + # setup the project and link required Qt modules setup_hifi_project(Network) # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) -copy_dlls_beside_windows_executable() \ No newline at end of file +copy_dlls_beside_windows_executable() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 16d8bacd23..d858673774 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -205,12 +205,6 @@ else (APPLE) endif() endif (APPLE) -copy_dlls_beside_windows_executable() +setup_memory_debugger() -if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) - if (UNIX) - MESSAGE("-- interface memory debugging is enabled.") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") - endif (UNIX) -endif () +copy_dlls_beside_windows_executable() diff --git a/libraries/animation/CMakeLists.txt b/libraries/animation/CMakeLists.txt index 8c75d5620c..fc7fa23dcc 100644 --- a/libraries/animation/CMakeLists.txt +++ b/libraries/animation/CMakeLists.txt @@ -3,4 +3,6 @@ set(TARGET_NAME animation) # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Script) -link_hifi_libraries(shared gpu model fbx) \ No newline at end of file +setup_memory_debugger() + +link_hifi_libraries(shared gpu model fbx) diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index 43a2016acf..c313aecbc0 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME audio-client) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Multimedia) @@ -25,4 +27,4 @@ if (APPLE) find_library(CoreAudio CoreAudio) find_library(CoreFoundation CoreFoundation) target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation}) -endif () \ No newline at end of file +endif () diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index c03f588d94..a0d40b1a10 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME audio) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network) diff --git a/libraries/auto-updater/CMakeLists.txt b/libraries/auto-updater/CMakeLists.txt index b3665af2cb..6960d8368d 100644 --- a/libraries/auto-updater/CMakeLists.txt +++ b/libraries/auto-updater/CMakeLists.txt @@ -1,3 +1,6 @@ set(TARGET_NAME auto-updater) + +setup_memory_debugger() + setup_hifi_library(Network) link_hifi_libraries(shared networking) diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index acc939b25c..b05c667c71 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME avatars) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Script) diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index 321b13f191..79b41fa957 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME display-plugins) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(OpenGL) @@ -31,4 +33,4 @@ if (WIN32) find_package(OpenVR REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) -endif() \ No newline at end of file +endif() diff --git a/libraries/embedded-webserver/CMakeLists.txt b/libraries/embedded-webserver/CMakeLists.txt index 955487e540..2d8915998b 100644 --- a/libraries/embedded-webserver/CMakeLists.txt +++ b/libraries/embedded-webserver/CMakeLists.txt @@ -1,4 +1,6 @@ set(TARGET_NAME embedded-webserver) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules -setup_hifi_library(Network) \ No newline at end of file +setup_hifi_library(Network) diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index c4dddb8971..3387715348 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -26,4 +26,6 @@ find_package(PolyVox REQUIRED) target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) +setup_memory_debugger() + link_hifi_libraries(shared gpu script-engine render render-utils) diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index f7936ff125..368257661e 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME entities) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Script) diff --git a/libraries/environment/CMakeLists.txt b/libraries/environment/CMakeLists.txt index a2ee9e3f55..fbdc614d26 100644 --- a/libraries/environment/CMakeLists.txt +++ b/libraries/environment/CMakeLists.txt @@ -7,4 +7,6 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared networking) \ No newline at end of file +setup_memory_debugger() + +link_hifi_libraries(shared networking) diff --git a/libraries/fbx/CMakeLists.txt b/libraries/fbx/CMakeLists.txt index 1ce1c74922..c06bb0efc1 100644 --- a/libraries/fbx/CMakeLists.txt +++ b/libraries/fbx/CMakeLists.txt @@ -7,4 +7,6 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared gpu model networking octree) \ No newline at end of file +setup_memory_debugger() + +link_hifi_libraries(shared gpu model networking octree) diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 7a88580f7f..84320297eb 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME gpu) +setup_memory_debugger() + AUTOSCRIBE_SHADER_LIB(gpu) # use setup_hifi_library macro to setup our project and link appropriate Qt modules diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index c3ded6c587..4428327deb 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -33,6 +33,8 @@ endif() #target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) #target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) +setup_memory_debugger() + # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) @@ -69,4 +71,4 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\") endif () endif () -endforeach() \ No newline at end of file +endforeach() diff --git a/libraries/model/CMakeLists.txt b/libraries/model/CMakeLists.txt index 563f347952..2099f83fec 100755 --- a/libraries/model/CMakeLists.txt +++ b/libraries/model/CMakeLists.txt @@ -1,7 +1,9 @@ set(TARGET_NAME model) - + AUTOSCRIBE_SHADER_LIB(gpu model) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library() diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index d79e6bde58..d0e0b850c7 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME networking) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network) @@ -29,4 +31,4 @@ target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES} ${TBB_LIBRARIES}) # append tbb includes to our list of includes to bubble target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) -include_application_version() \ No newline at end of file +include_application_version() diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index cc36aead15..8b9ff6bda2 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME octree) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library() diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index b1f9fbb79c..802665b948 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME physics) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library() diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index 28b136ccf4..98fd5fdc93 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(shared) add_dependency_external_projects(glm) find_package(GLM REQUIRED) + +setup_memory_debugger() + target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) - - diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 0ea71e54e3..ceb1a192ab 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -40,4 +40,6 @@ add_dependency_external_projects(oglplus) find_package(OGLPLUS REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) +setup_memory_debugger() + link_hifi_libraries(animation fbx shared gpu model render environment) diff --git a/libraries/render/CMakeLists.txt b/libraries/render/CMakeLists.txt index 4d2be949e6..1f73d93519 100644 --- a/libraries/render/CMakeLists.txt +++ b/libraries/render/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME render) +setup_memory_debugger() + AUTOSCRIBE_SHADER_LIB(gpu model) # use setup_hifi_library macro to setup our project and link appropriate Qt modules @@ -21,4 +23,4 @@ if (WIN32) target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") endif () endif() -endif (WIN32) \ No newline at end of file +endif (WIN32) diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 99d9149c3a..6bb53389af 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME script-engine) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Gui Network Script Widgets) diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 00a80619bc..a80f4194ef 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME shared) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules # TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp) setup_hifi_library(Gui Network Script Widgets) diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index 1aefc99c78..68caa940c1 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(render-utils shared) add_dependency_external_projects(glm) find_package(GLM REQUIRED) + +setup_memory_debugger() + target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) - - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1593b649a0..c2dc30c4bb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,3 +35,5 @@ set_target_properties("all-tests" PROPERTIES FOLDER "hidden/test-targets") set_target_properties("all-tests" PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE EXCLUDE_FROM_ALL TRUE) + +setup_memory_debugger() diff --git a/tests/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index 2e9dbc9424..bc1e93a94f 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -6,4 +6,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () +setup_memory_debugger() + setup_hifi_testcase() diff --git a/tests/audio/CMakeLists.txt b/tests/audio/CMakeLists.txt index 8e894e929e..c56ef049bd 100644 --- a/tests/audio/CMakeLists.txt +++ b/tests/audio/CMakeLists.txt @@ -6,4 +6,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt index 0077549100..f83efe7f64 100644 --- a/tests/entities/CMakeLists.txt +++ b/tests/entities/CMakeLists.txt @@ -9,4 +9,6 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation environment) -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/tests/jitter/CMakeLists.txt b/tests/jitter/CMakeLists.txt index 7b636aa87f..ba46582b02 100644 --- a/tests/jitter/CMakeLists.txt +++ b/tests/jitter/CMakeLists.txt @@ -7,4 +7,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro() -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/networking/CMakeLists.txt b/tests/networking/CMakeLists.txt index 3be2fff027..fcf32d89c8 100644 --- a/tests/networking/CMakeLists.txt +++ b/tests/networking/CMakeLists.txt @@ -7,4 +7,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/octree/CMakeLists.txt b/tests/octree/CMakeLists.txt index a605a4088b..77511c682a 100644 --- a/tests/octree/CMakeLists.txt +++ b/tests/octree/CMakeLists.txt @@ -7,4 +7,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase(Script Network) \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase(Script Network) diff --git a/tests/physics/CMakeLists.txt b/tests/physics/CMakeLists.txt index 36cf21c681..1a6f49430b 100644 --- a/tests/physics/CMakeLists.txt +++ b/tests/physics/CMakeLists.txt @@ -21,4 +21,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES) copy_dlls_beside_windows_executable() endmacro () +setup_memory_debugger() + setup_hifi_testcase(Script) diff --git a/tests/render-utils/CMakeLists.txt b/tests/render-utils/CMakeLists.txt index 97d3214744..1b47f85099 100644 --- a/tests/render-utils/CMakeLists.txt +++ b/tests/render-utils/CMakeLists.txt @@ -9,4 +9,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(render-utils gpu shared) message(${PROJECT_BINARY_DIR}) + +setup_memory_debugger() + copy_dlls_beside_windows_executable() diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt index eefdf2aa3a..3ee9f4ae9f 100644 --- a/tests/shaders/CMakeLists.txt +++ b/tests/shaders/CMakeLists.txt @@ -18,4 +18,7 @@ include_directories("${PROJECT_BINARY_DIR}/../../libraries/entities-renderer/") include_directories("${PROJECT_BINARY_DIR}/../../libraries/model/") message(${PROJECT_BINARY_DIR}) + +setup_memory_debugger() + copy_dlls_beside_windows_executable() diff --git a/tests/shared/CMakeLists.txt b/tests/shared/CMakeLists.txt index bc6eab0212..c3d6cfd810 100644 --- a/tests/shared/CMakeLists.txt +++ b/tests/shared/CMakeLists.txt @@ -8,4 +8,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/ui/CMakeLists.txt b/tests/ui/CMakeLists.txt index ad1009d925..c0f20a280f 100644 --- a/tests/ui/CMakeLists.txt +++ b/tests/ui/CMakeLists.txt @@ -13,4 +13,6 @@ endif() # link in the shared libraries link_hifi_libraries(ui render-utils gpu shared) -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 55994f3d89..4d8618b37c 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,3 +1,5 @@ +setup_memory_debugger() + # add the tool directories add_subdirectory(mtc) set_target_properties(mtc PROPERTIES FOLDER "Tools") @@ -7,4 +9,3 @@ set_target_properties(scribe PROPERTIES FOLDER "Tools") add_subdirectory(vhacd-util) set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") - diff --git a/tools/mtc/CMakeLists.txt b/tools/mtc/CMakeLists.txt index 5c598eaf0b..fe5f1920bc 100644 --- a/tools/mtc/CMakeLists.txt +++ b/tools/mtc/CMakeLists.txt @@ -1,4 +1,6 @@ set(TARGET_NAME mtc) setup_hifi_project() -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/tools/scribe/CMakeLists.txt b/tools/scribe/CMakeLists.txt index b71a287e46..0abf70f727 100755 --- a/tools/scribe/CMakeLists.txt +++ b/tools/scribe/CMakeLists.txt @@ -1,2 +1,5 @@ set(TARGET_NAME scribe) -setup_hifi_project() \ No newline at end of file + +setup_memory_debugger() + +setup_hifi_project() diff --git a/tools/vhacd-util/CMakeLists.txt b/tools/vhacd-util/CMakeLists.txt index c94b2ad083..b79a6a1893 100644 --- a/tools/vhacd-util/CMakeLists.txt +++ b/tools/vhacd-util/CMakeLists.txt @@ -8,6 +8,8 @@ find_package(VHACD REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${VHACD_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${VHACD_LIBRARIES}) +setup_memory_debugger() + if (UNIX AND NOT APPLE) include(FindOpenMP) if(OPENMP_FOUND) From 0ac885da80ab10197608c5c7f902d6259deffc63 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 20 Aug 2015 10:17:13 -0700 Subject: [PATCH 088/170] fix when memory debugging message is printed --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc4c938f08..b2f35b1443 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,6 +215,9 @@ if (ANDROID OR DESKTOP_GVR) endif () if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + SET( HIFI_MEMORY_DEBUGGING true ) +endif () +if (HIFI_MEMORY_DEBUGGING) if (UNIX) MESSAGE("-- Memory debugging is enabled") endif (UNIX) From aac15e52bb223fac707f9c2f4e77ab6982ef11d1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Aug 2015 10:19:34 -0700 Subject: [PATCH 089/170] add explicit cast to double for uptime --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 369cb3b761..23957380e6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1502,7 +1502,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket()); // add the node uptime in our list - nodeJson[JSON_KEY_UPTIME] = QString::number((QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0); + nodeJson[JSON_KEY_UPTIME] = QString::number(double(QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0); // if the node has pool information, add it DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); From f8344183846c6e1a17b57d004f8af86fa0e94309 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 20 Aug 2015 11:23:16 -0700 Subject: [PATCH 090/170] Fix edit.js to warn when creating entities outside of world bounds --- examples/edit.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index deda035d5e..988e0a04b1 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1036,7 +1036,6 @@ function handeMenuEvent(menuItem) { // This function tries to find a reasonable position to place a new entity based on the camera // position. If a reasonable position within the world bounds can't be found, `null` will // be returned. The returned position will also take into account grid snapping settings. -// FIXME - technically we should guard against very large positions too function getPositionToCreateEntity() { var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE; var direction = Quat.getFront(Camera.orientation); @@ -1047,18 +1046,20 @@ function getPositionToCreateEntity() { var HALF_TREE_SCALE = 16384; - var cameraOutOfBounds = cameraPosition.x < -HALF_TREE_SCALE || cameraPosition.y < -HALF_TREE_SCALE || - cameraPosition.z < -HALF_TREE_SCALE; - var placementOutOfBounds = placementPosition.x < -HALF_TREE_SCALE || placementPosition.y < -HALF_TREE_SCALE || - placementPosition.z < -HALF_TREE_SCALE; + var cameraOutOfBounds = Math.abs(cameraPosition.x) > HALF_TREE_SCALE + || Math.abs(cameraPosition.y) > HALF_TREE_SCALE + || Math.abs(cameraPosition.z) > HALF_TREE_SCALE; + var placementOutOfBounds = Math.abs(placementPosition.x) > HALF_TREE_SCALE + || Math.abs(placementPosition.y) > HALF_TREE_SCALE + || Math.abs(placementPosition.z) > HALF_TREE_SCALE; if (cameraOutOfBounds && placementOutOfBounds) { return null; } - placementPosition.x = Math.max(-HALF_TREE_SCALE, placementPosition.x); - placementPosition.y = Math.max(-HALF_TREE_SCALE, placementPosition.y); - placementPosition.z = Math.max(-HALF_TREE_SCALE, placementPosition.z); + placementPosition.x = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.x)); + placementPosition.y = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.y)); + placementPosition.z = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.z)); return placementPosition; } From e93b360908141742750fe10a231bc41e6132effa Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Aug 2015 12:12:43 -0700 Subject: [PATCH 091/170] expose options property to AI script interface --- libraries/audio/src/AudioInjector.cpp | 30 +++++++++---------- libraries/audio/src/AudioInjector.h | 9 +++--- libraries/avatars/src/Player.cpp | 2 +- .../script-engine/src/ScriptAudioInjector.h | 6 ++-- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 22d57176a5..8fd7cb9ce5 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -77,9 +77,9 @@ void AudioInjector::injectAudio() { int byteOffset = (int) floorf(AudioConstants::SAMPLE_RATE * _options.secondOffset * (_options.stereo ? 2.0f : 1.0f)); byteOffset *= sizeof(int16_t); - _currentSendPosition = byteOffset; + _currentSendOffset = byteOffset; } else { - _currentSendPosition = 0; + _currentSendOffset = 0; } if (_options.localOnly) { @@ -119,7 +119,7 @@ void AudioInjector::injectLocally() { _localBuffer->setVolume(_options.volume); // give our current send position to the local buffer - _localBuffer->setCurrentOffset(_currentSendPosition); + _localBuffer->setCurrentOffset(_currentSendOffset); success = _localAudioInterface->outputLocalInjector(_options.stereo, this); @@ -144,9 +144,9 @@ void AudioInjector::injectLocally() { const uchar MAX_INJECTOR_VOLUME = 0xFF; void AudioInjector::injectToMixer() { - if (_currentSendPosition < 0 || - _currentSendPosition >= _audioData.size()) { - _currentSendPosition = 0; + if (_currentSendOffset < 0 || + _currentSendOffset >= _audioData.size()) { + _currentSendOffset = 0; } auto nodeList = DependencyManager::get(); @@ -203,15 +203,15 @@ void AudioInjector::injectToMixer() { // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks quint16 outgoingInjectedAudioSequenceNumber = 0; - while (_currentSendPosition < _audioData.size() && !_shouldStop) { + while (_currentSendOffset < _audioData.size() && !_shouldStop) { int bytesToCopy = std::min(((_options.stereo) ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, - _audioData.size() - _currentSendPosition); + _audioData.size() - _currentSendOffset); // Measure the loudness of this frame _loudness = 0.0f; for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) { - _loudness += abs(*reinterpret_cast(_audioData.data() + _currentSendPosition + i)) / + _loudness += abs(*reinterpret_cast(_audioData.data() + _currentSendOffset + i)) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); } _loudness /= (float)(bytesToCopy / sizeof(int16_t)); @@ -220,7 +220,7 @@ void AudioInjector::injectToMixer() { // pack the sequence number audioPacket->writePrimitive(outgoingInjectedAudioSequenceNumber); - + audioPacket->seek(positionOptionOffset); audioPacket->writePrimitive(_options.position); audioPacket->writePrimitive(_options.orientation); @@ -232,7 +232,7 @@ void AudioInjector::injectToMixer() { audioPacket->seek(audioDataOffset); // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet - audioPacket->write(_audioData.data() + _currentSendPosition, bytesToCopy); + audioPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy); // set the correct size used for this packet audioPacket->setPayloadSize(audioPacket->pos()); @@ -246,11 +246,11 @@ void AudioInjector::injectToMixer() { outgoingInjectedAudioSequenceNumber++; } - _currentSendPosition += bytesToCopy; + _currentSendOffset += bytesToCopy; // send two packets before the first sleep so the mixer can start playback right away - if (_currentSendPosition != bytesToCopy && _currentSendPosition < _audioData.size()) { + if (_currentSendOffset != bytesToCopy && _currentSendOffset < _audioData.size()) { // process events in case we have been told to stop and be deleted QCoreApplication::processEvents(); @@ -268,8 +268,8 @@ void AudioInjector::injectToMixer() { } } - if (shouldLoop && _currentSendPosition >= _audioData.size()) { - _currentSendPosition = 0; + if (shouldLoop && _currentSendOffset >= _audioData.size()) { + _currentSendOffset = 0; } } } diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 88d3f1e151..d65925b865 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -31,7 +31,6 @@ class AbstractAudioInterface; class AudioInjector : public QObject { Q_OBJECT - Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: AudioInjector(QObject* parent); AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions); @@ -39,7 +38,8 @@ public: bool isFinished() const { return _isFinished; } - int getCurrentSendPosition() const { return _currentSendPosition; } + int getCurrentSendOffset() const { return _currentSendOffset; } + void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; } AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; } bool isLocalOnly() const { return _options.localOnly; } @@ -58,9 +58,8 @@ public slots: void stopAndDeleteLater(); const AudioInjectorOptions& getOptions() const { return _options; } - void setOptions(const AudioInjectorOptions& options) { _options = options; } + void setOptions(const AudioInjectorOptions& options) { _options = options; } - void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; } float getLoudness() const { return _loudness; } bool isPlaying() const { return _isPlaying; } void restartPortionAfterFinished(); @@ -82,7 +81,7 @@ private: bool _isStarted = false; bool _isFinished = false; bool _shouldDeleteAfterFinish = false; - int _currentSendPosition = 0; + int _currentSendOffset = 0; AbstractAudioInterface* _localAudioInterface = NULL; AudioInjectorLocalBuffer* _localBuffer = NULL; }; diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp index e7d94f0735..29544924b2 100644 --- a/libraries/avatars/src/Player.cpp +++ b/libraries/avatars/src/Player.cpp @@ -371,7 +371,7 @@ void Player::setAudioInjectorPosition() { int MSEC_PER_SEC = 1000; int FRAME_SIZE = sizeof(AudioConstants::AudioSample) * _recording->numberAudioChannel(); int currentAudioFrame = elapsed() * FRAME_SIZE * (AudioConstants::SAMPLE_RATE / MSEC_PER_SEC); - _injector->setCurrentSendPosition(currentAudioFrame); + _injector->setCurrentSendOffset(currentAudioFrame); } void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) { diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 92bc5d31da..0d16b26fdf 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -21,13 +21,15 @@ class ScriptAudioInjector : public QObject { Q_PROPERTY(bool isPlaying READ isPlaying) Q_PROPERTY(float loudness READ getLoudness) + Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: ScriptAudioInjector(AudioInjector* injector); ~ScriptAudioInjector(); public slots: void restart() { _injector->restart(); } void stop() { _injector->stop(); } - + + const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); } float getLoudness() const { return _injector->getLoudness(); } @@ -49,4 +51,4 @@ Q_DECLARE_METATYPE(ScriptAudioInjector*) QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in); void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& out); -#endif // hifi_ScriptAudioInjector_h \ No newline at end of file +#endif // hifi_ScriptAudioInjector_h From f26849c7e15a25b836308f9c3a613e43ef48eded Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 20 Aug 2015 12:34:50 -0700 Subject: [PATCH 092/170] Proper HMD scaling. --- interface/src/avatar/MyAvatar.cpp | 40 +++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 52473a6d47..cd968c3d59 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -846,7 +846,7 @@ void MyAvatar::sendKillAvatar() { void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head - // + // And set the correctedLookAt for all (nearby) avatars that are looking at me. _lookAtTargetAvatar.reset(); _targetAvatarPosition = glm::vec3(0.0f); @@ -870,14 +870,39 @@ void MyAvatar::updateLookAtTargetAvatar() { smallestAngleTo = angleTo; } if (Application::getInstance()->isLookingAtMyAvatar(avatar)) { + // Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face. // Offset their gaze according to whether they're looking at one of my eyes or my mouth. - glm::vec3 gazeOffset = avatar->getHead()->getLookAtPosition() - getHead()->getEyePosition(); - const float HUMAN_EYE_SEPARATION = 0.065f; - float myEyeSeparation = glm::length(getHead()->getLeftEyePosition() - getHead()->getRightEyePosition()); - gazeOffset = gazeOffset * HUMAN_EYE_SEPARATION / myEyeSeparation; - avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() - + gazeOffset); + + // The camera isn't at the point midway between the avatar eyes. (Even without an HMD, the head can be offset a bit.) + // First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point. + // (We will be adding that offset to the camera position, after making some other adjustments.) + glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); + glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition(); + + // Scale by proportional differences between avatar and human. + glm::mat4 leftEye = Application::getInstance()->getEyeOffset(Eye::Left); // Pose? + glm::mat4 rightEye = Application::getInstance()->getEyeOffset(Eye::Right); + glm::vec3 leftEyePosition = glm::vec3(leftEye[3]); + glm::vec3 rightEyePosition = glm::vec3(rightEye[3]); + float humanEyeSeparationInModelSpace = glm::length(leftEyePosition - rightEyePosition); + float avatarEyeSeparation = glm::length(getHead()->getLeftEyePosition() - getHead()->getRightEyePosition()); + gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; + + // If the camera is also not oriented with the head, adjust by getting the offset in head-space... + /* Not needed (i.e., code is a no-op), but I'm leaving the example code here in case something like this is needed someday. + glm::quat avatarHeadOrientation = getHead()->getOrientation(); + glm::vec3 gazeOffsetLocalToHead = glm::inverse(avatarHeadOrientation) * gazeOffset; + // ... and treat that as though it were in camera space, bringing it back to world space. + // But camera is fudged to make the picture feel like the avatar's orientation. + glm::quat humanOrientation = Application::getInstance()->getViewFrustum()->getOrientation(); // or just avatar getOrienation() ? + gazeOffset = humanOrientation * gazeOffsetLocalToHead; + */ + + // And now we can finally add that offset to the camera. + glm::vec3 corrected = Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset; + avatar->getHead()->setCorrectedLookAtPosition(corrected); + } else { avatar->getHead()->clearCorrectedLookAtPosition(); } @@ -1114,6 +1139,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl getHead()->render(renderArgs, 1.0f, renderFrustum); } + // This is drawing the lookat vectors from our avatar to wherever we're looking. if (qApp->isHMDMode()) { glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); From e9b3d481650e7fdfa1baa6044b07677fc22aba0b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Aug 2015 16:10:31 -0700 Subject: [PATCH 093/170] Add menu item that disables adjusting eyelids to follow pupil Developer > Avatar > Disable Eyelid Adjustment --- interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/avatar/Head.cpp | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f074dc5ac7..11bc38c85e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -444,6 +444,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Connexion, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index ca46b80f92..278da363d1 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -168,6 +168,7 @@ namespace MenuOption { const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; + const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment"; const QString DisableLightEntities = "Disable Light Entities"; const QString DisableNackPackets = "Disable Entity NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 9ab2c83a79..d645253eab 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -277,6 +277,10 @@ void Head::calculateMouthShapes() { void Head::applyEyelidOffset(glm::quat headOrientation) { // Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches. + if (Menu::getInstance()->isOptionChecked(MenuOption::DisableEyelidAdjustment)) { + return; + } + glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FRONT, getLookAtPosition() - _eyePosition); eyeRotation = eyeRotation * glm::angleAxis(safeEulerAngles(headOrientation).y, IDENTITY_UP); // Rotation w.r.t. head float eyePitch = safeEulerAngles(eyeRotation).x; From 05f4145acb97974b8fb40a85ac28e2a333b647c0 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 20 Aug 2015 16:28:40 -0700 Subject: [PATCH 094/170] Checkpoint. Working? --- interface/src/avatar/MyAvatar.cpp | 54 ++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index cd968c3d59..1b8a3b2f89 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -843,6 +843,7 @@ void MyAvatar::sendKillAvatar() { DependencyManager::get()->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::AvatarMixer); } +static int counter = 0; void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head @@ -872,21 +873,34 @@ void MyAvatar::updateLookAtTargetAvatar() { if (Application::getInstance()->isLookingAtMyAvatar(avatar)) { // Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face. - // Offset their gaze according to whether they're looking at one of my eyes or my mouth. + glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); // A position, in world space, on my avatar. // The camera isn't at the point midway between the avatar eyes. (Even without an HMD, the head can be offset a bit.) + // Let's get everything to world space: + glm::vec3 avatarLeftEye = getHead()->getLeftEyePosition(); + glm::vec3 avatarRightEye = getHead()->getRightEyePosition(); + // When not in HMD, these might both answer identity (i.e., the bridge of the nose). That's ok. + // By my inpsection of the code and live testing, getEyeOffset and getEyePose are the same. (Application hands identity as offset matrix.) + glm::mat4 leftEye = Application::getInstance()->getEyeOffset(Eye::Left); + glm::mat4 rightEye = Application::getInstance()->getEyeOffset(Eye::Right); + glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); + glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); + auto humanSystem = Application::getInstance()->getViewFrustum(); + glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal); + glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal); + + // debugging or some code paths + glm::vec3 avatarAverage = avatarLeftEye + ((avatarRightEye - avatarLeftEye) * 0.5f); + glm::vec3 humanAverage = humanLeftEye + ((humanRightEye - humanLeftEye) * 0.5f); + +#if 1 // First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point. // (We will be adding that offset to the camera position, after making some other adjustments.) - glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition(); // Scale by proportional differences between avatar and human. - glm::mat4 leftEye = Application::getInstance()->getEyeOffset(Eye::Left); // Pose? - glm::mat4 rightEye = Application::getInstance()->getEyeOffset(Eye::Right); - glm::vec3 leftEyePosition = glm::vec3(leftEye[3]); - glm::vec3 rightEyePosition = glm::vec3(rightEye[3]); - float humanEyeSeparationInModelSpace = glm::length(leftEyePosition - rightEyePosition); - float avatarEyeSeparation = glm::length(getHead()->getLeftEyePosition() - getHead()->getRightEyePosition()); + float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye); + float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye); gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; // If the camera is also not oriented with the head, adjust by getting the offset in head-space... @@ -895,13 +909,30 @@ void MyAvatar::updateLookAtTargetAvatar() { glm::vec3 gazeOffsetLocalToHead = glm::inverse(avatarHeadOrientation) * gazeOffset; // ... and treat that as though it were in camera space, bringing it back to world space. // But camera is fudged to make the picture feel like the avatar's orientation. - glm::quat humanOrientation = Application::getInstance()->getViewFrustum()->getOrientation(); // or just avatar getOrienation() ? + glm::quat humanOrientation = humanSystem->getOrientation(); // or just avatar getOrienation() ? gazeOffset = humanOrientation * gazeOffsetLocalToHead; - */ + glm::vec3 corrected = humanSystem->getPosition() + gazeOffset; + */ // And now we can finally add that offset to the camera. glm::vec3 corrected = Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset; +#else + //glm::vec3 gazeOffset = ((humanRightEye - avatarRightEye) + (humanLeftEye - avatarLeftEye)) * 0.5f; + glm::vec3 gazeOffset = humanAverage - avatarAverage; + glm::vec3 corrected = lookAtPosition + gazeOffset; +#endif avatar->getHead()->setCorrectedLookAtPosition(corrected); + + if (counter++ > 60) { + counter = 0; + qCDebug(interfaceapp) << Application::getInstance()->isHMDMode(); + qCDebug(interfaceapp) << "camera:" << Application::getInstance()->getViewFrustum()->getPosition() << "delta from av human:" << (humanAverage - Application::getInstance()->getViewFrustum()->getPosition()); + + qCDebug(interfaceapp) << "lt avatar:" << avatarLeftEye << " lt human:" << humanLeftEye; + qCDebug(interfaceapp) << "rt avatar:" << avatarRightEye << " rt human:" << humanRightEye; + qCDebug(interfaceapp) << "av avatar:" << avatarAverage << " av humn:" << humanAverage; + qCDebug(interfaceapp) << "offset:" << gazeOffset << " corrected:" << corrected << " from:" << lookAtPosition; + } } else { avatar->getHead()->clearCorrectedLookAtPosition(); @@ -1140,6 +1171,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl } // This is drawing the lookat vectors from our avatar to wherever we're looking. + /* if (qApp->isHMDMode()) { glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); @@ -1155,7 +1187,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl cameraPosition + getOrientation() * (rightEyePosition - headPosition)); } else { getHead()->renderLookAts(renderArgs); - } + }*/ getHand()->render(renderArgs, true); } From ea02583875178380c429046fd5e8ef3fcfb8258a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 20 Aug 2015 17:33:10 -0700 Subject: [PATCH 095/170] Cleanup. --- interface/src/avatar/MyAvatar.cpp | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1b8a3b2f89..501c7eb8d5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -881,6 +881,7 @@ void MyAvatar::updateLookAtTargetAvatar() { glm::vec3 avatarRightEye = getHead()->getRightEyePosition(); // When not in HMD, these might both answer identity (i.e., the bridge of the nose). That's ok. // By my inpsection of the code and live testing, getEyeOffset and getEyePose are the same. (Application hands identity as offset matrix.) + // This might be more work than needed for any given use, but as we explore different formulations, we go mad if we don't work in world space. glm::mat4 leftEye = Application::getInstance()->getEyeOffset(Eye::Left); glm::mat4 rightEye = Application::getInstance()->getEyeOffset(Eye::Right); glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); @@ -888,12 +889,8 @@ void MyAvatar::updateLookAtTargetAvatar() { auto humanSystem = Application::getInstance()->getViewFrustum(); glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal); glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal); - - // debugging or some code paths - glm::vec3 avatarAverage = avatarLeftEye + ((avatarRightEye - avatarLeftEye) * 0.5f); - glm::vec3 humanAverage = humanLeftEye + ((humanRightEye - humanLeftEye) * 0.5f); -#if 1 + // First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point. // (We will be adding that offset to the camera position, after making some other adjustments.) glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition(); @@ -916,23 +913,8 @@ void MyAvatar::updateLookAtTargetAvatar() { // And now we can finally add that offset to the camera. glm::vec3 corrected = Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset; -#else - //glm::vec3 gazeOffset = ((humanRightEye - avatarRightEye) + (humanLeftEye - avatarLeftEye)) * 0.5f; - glm::vec3 gazeOffset = humanAverage - avatarAverage; - glm::vec3 corrected = lookAtPosition + gazeOffset; -#endif + avatar->getHead()->setCorrectedLookAtPosition(corrected); - - if (counter++ > 60) { - counter = 0; - qCDebug(interfaceapp) << Application::getInstance()->isHMDMode(); - qCDebug(interfaceapp) << "camera:" << Application::getInstance()->getViewFrustum()->getPosition() << "delta from av human:" << (humanAverage - Application::getInstance()->getViewFrustum()->getPosition()); - - qCDebug(interfaceapp) << "lt avatar:" << avatarLeftEye << " lt human:" << humanLeftEye; - qCDebug(interfaceapp) << "rt avatar:" << avatarRightEye << " rt human:" << humanRightEye; - qCDebug(interfaceapp) << "av avatar:" << avatarAverage << " av humn:" << humanAverage; - qCDebug(interfaceapp) << "offset:" << gazeOffset << " corrected:" << corrected << " from:" << lookAtPosition; - } } else { avatar->getHead()->clearCorrectedLookAtPosition(); From 642e56903329bbbe3c70d0795f40a2fc3f7913f7 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 20 Aug 2015 17:38:49 -0700 Subject: [PATCH 096/170] Uncomment code that was commented out to simplify debugging. --- interface/src/avatar/MyAvatar.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 501c7eb8d5..eada41eb29 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1153,7 +1153,6 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl } // This is drawing the lookat vectors from our avatar to wherever we're looking. - /* if (qApp->isHMDMode()) { glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); @@ -1169,7 +1168,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl cameraPosition + getOrientation() * (rightEyePosition - headPosition)); } else { getHead()->renderLookAts(renderArgs); - }*/ + } getHand()->render(renderArgs, true); } From 1b3d7fabc8a4ac6021441d671d249885ee4c741d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 7 Aug 2015 17:58:43 -0700 Subject: [PATCH 097/170] ResourceCache, NetworkGeometry and Model refactoring and optimizations. * Removed validation logic from Resource class, Qt does this internally and is more standards compliant. This should result in more accurate caching and faster resource fetching when cache is stale and validation fails. * Added loaded and failed slots to Resource class, so it does not have to be polled. * NetworkGeometry now uses multiple Resource objects to download the fst/mapping file and the fbx/obj models. * NetworkGeometry is no longer a subclass of Resource * NetworkGeometry now has signals for success and failure, you no longer have to poll it to determine when loading is complete (except for textures *sigh*) Some functionality was removed * NetworkGeometry no longer has a fallback * NetworkGeometry no longer loads LODs or has lod logic. * The number of FBXGeometry copies is greatly reduced. * Model::setURL no supports fallback URL, delayLoad or retainCurrent option. This can result in a pop when switching avatars, and there's no longer a default if avatar loading fails. --- interface/src/ModelPackager.cpp | 11 +- interface/src/ModelPackager.h | 4 +- interface/src/avatar/Avatar.cpp | 24 +- interface/src/avatar/Head.cpp | 3 - libraries/animation/src/AnimationCache.cpp | 17 +- libraries/animation/src/AnimationCache.h | 9 +- .../src/RenderableZoneEntityItem.cpp | 4 +- libraries/fbx/src/FBXReader.cpp | 22 +- libraries/fbx/src/FBXReader.h | 4 +- libraries/fbx/src/OBJReader.cpp | 9 +- libraries/fbx/src/OBJReader.h | 4 +- libraries/networking/src/ResourceCache.cpp | 115 +-- libraries/networking/src/ResourceCache.h | 17 +- libraries/render-utils/src/GeometryCache.cpp | 806 +++++++----------- libraries/render-utils/src/GeometryCache.h | 145 ++-- libraries/render-utils/src/Model.cpp | 175 +--- libraries/render-utils/src/Model.h | 21 +- tests/networking/src/ResourceTests.cpp | 95 +++ tests/networking/src/ResourceTests.h | 23 + tools/vhacd-util/src/VHACDUtil.cpp | 7 +- 20 files changed, 682 insertions(+), 833 deletions(-) create mode 100644 tests/networking/src/ResourceTests.cpp create mode 100644 tests/networking/src/ResourceTests.h diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 09d572c31d..0b564f3574 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -106,16 +106,17 @@ bool ModelPackager::loadModel() { } qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath(); QByteArray fbxContents = fbx.readAll(); - _geometry = readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()); - + + _geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath())); + // make sure we have some basic mappings - populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry); + populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry); return true; } bool ModelPackager::editProperties() { // open the dialog to configure the rest - ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry); + ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_geometry); if (properties.exec() == QDialog::Rejected) { return false; } @@ -339,7 +340,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename void ModelPackager::listTextures() { _textures.clear(); - foreach (FBXMesh mesh, _geometry.meshes) { + foreach (FBXMesh mesh, _geometry->meshes) { foreach (FBXMeshPart part, mesh.parts) { if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() && !_textures.contains(part.diffuseTexture.filename)) { diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index c681ae436f..10942833f9 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -39,11 +39,11 @@ private: QString _texDir; QVariantHash _mapping; - FBXGeometry _geometry; + std::unique_ptr _geometry; QStringList _textures; }; -#endif // hifi_ModelPackager_h \ No newline at end of file +#endif // hifi_ModelPackager_h diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index c1a1d11268..19f84018f8 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -196,7 +196,6 @@ void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("hand"); getHand()->simulate(deltaTime, false); } - _skeletonModel.setLODDistance(getLODDistance()); if (!_shouldRenderBillboard && inViewFrustum) { { @@ -562,24 +561,22 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { } void Avatar::fixupModelsInScene() { - if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { - return; - } // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene render::ScenePointer scene = Application::getInstance()->getMain3DScene(); render::PendingChanges pendingChanges; - if (_skeletonModel.needsFixupInScene()) { + if (_skeletonModel.isRenderable() && _skeletonModel.needsFixupInScene()) { _skeletonModel.removeFromScene(scene, pendingChanges); _skeletonModel.addToScene(scene, pendingChanges); } - if (getHead()->getFaceModel().needsFixupInScene()) { - getHead()->getFaceModel().removeFromScene(scene, pendingChanges); - getHead()->getFaceModel().addToScene(scene, pendingChanges); + Model& faceModel = getHead()->getFaceModel(); + if (faceModel.isRenderable() && faceModel.needsFixupInScene()) { + faceModel.removeFromScene(scene, pendingChanges); + faceModel.addToScene(scene, pendingChanges); } for (auto attachmentModel : _attachmentModels) { - if (attachmentModel->needsFixupInScene()) { + if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges); } @@ -621,11 +618,8 @@ void Avatar::simulateAttachments(float deltaTime) { int jointIndex = getJointIndex(attachment.jointName); glm::vec3 jointPosition; glm::quat jointRotation; - if (!isMyAvatar()) { - model->setLODDistance(getLODDistance()); - } if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && - _skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) { + _skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) { model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale @@ -978,12 +972,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { void Avatar::setFaceModelURL(const QUrl& faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); - getHead()->getFaceModel().setURL(_faceModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar()); + getHead()->getFaceModel().setURL(_faceModelURL); } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); - _skeletonModel.setURL(_skeletonModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar()); + _skeletonModel.setURL(_skeletonModelURL); } void Avatar::setAttachmentData(const QVector& attachmentData) { diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index d645253eab..3806dd6edc 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -233,9 +233,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { _saccade = glm::vec3(); } - if (!isMine) { - _faceModel.setLODDistance(static_cast(_owningAvatar)->getLODDistance()); - } _leftEyePosition = _rightEyePosition = getPosition(); if (!billboard) { _faceModel.simulate(deltaTime); diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 634e0589b7..7f3f393a8b 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -13,6 +13,7 @@ #include #include "AnimationCache.h" +#include "AnimationLogging.h" static int animationPointerMetaTypeId = qRegisterMetaType(); @@ -62,11 +63,15 @@ void AnimationReader::run() { QSharedPointer animation = _animation.toStrongRef(); if (!animation.isNull()) { QMetaObject::invokeMethod(animation.data(), "setGeometry", - Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString()))); + Q_ARG(FBXGeometry*, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString()))); } _reply->deleteLater(); } +bool Animation::isLoaded() const { + return _loaded && _geometry; +} + QStringList Animation::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; @@ -75,7 +80,7 @@ QStringList Animation::getJointNames() const { return result; } QStringList names; - foreach (const FBXJoint& joint, _geometry.joints) { + foreach (const FBXJoint& joint, _geometry->joints) { names.append(joint.name); } return names; @@ -88,15 +93,15 @@ QVector Animation::getFrames() const { Q_RETURN_ARG(QVector, result)); return result; } - return _geometry.animationFrames; + return _geometry->animationFrames; } const QVector& Animation::getFramesReference() const { - return _geometry.animationFrames; + return _geometry->animationFrames; } -void Animation::setGeometry(const FBXGeometry& geometry) { - _geometry = geometry; +void Animation::setGeometry(FBXGeometry* geometry) { + _geometry.reset(geometry); finishedLoading(true); } diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 6a0a77f659..3ff5957fa2 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -52,7 +52,10 @@ public: Animation(const QUrl& url); - const FBXGeometry& getGeometry() const { return _geometry; } + const FBXGeometry& getGeometry() const { return *_geometry; } + + virtual bool isLoaded() const override; + Q_INVOKABLE QStringList getJointNames() const; @@ -62,13 +65,13 @@ public: protected: - Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); + Q_INVOKABLE void setGeometry(FBXGeometry* geometry); virtual void downloadFinished(QNetworkReply* reply); private: - FBXGeometry _geometry; + std::unique_ptr _geometry; }; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index bb0a35f7b0..930a684617 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -38,7 +38,7 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) { _model = getModel(); _needsInitialSimulation = true; - _model->setURL(getCompoundShapeURL(), QUrl(), true, true); + _model->setURL(getCompoundShapeURL()); } if (oldPosition != getPosition() || oldRotation != getRotation() || @@ -85,7 +85,7 @@ void RenderableZoneEntityItem::initialSimulation() { void RenderableZoneEntityItem::updateGeometry() { if (_model && !_model->isActive() && hasCompoundShapeURL()) { // Since we have a delayload, we need to update the geometry if it has been downloaded - _model->setURL(getCompoundShapeURL(), QUrl(), true); + _model->setURL(getCompoundShapeURL()); } if (_model && _model->isActive() && _needsInitialSimulation) { initialSimulation(); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 2db5f5fa51..f0d13f8792 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1373,12 +1373,12 @@ FBXLight extractLight(const FBXNode& object) { #if USE_MODEL_MESH -void buildModelMesh(ExtractedMesh& extracted) { +void buildModelMesh(ExtractedMesh& extracted, const QString& url) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); if (extracted.mesh.vertices.size() == 0) { extracted.mesh._mesh = model::Mesh(); - qCDebug(modelformat) << "buildModelMesh failed -- no vertices"; + qCDebug(modelformat) << "buildModelMesh failed -- no vertices, url = " << url; return; } FBXMesh& fbxMesh = extracted.mesh; @@ -1465,7 +1465,7 @@ void buildModelMesh(ExtractedMesh& extracted) { if (! totalIndices) { extracted.mesh._mesh = model::Mesh(); - qCDebug(modelformat) << "buildModelMesh failed -- no indices"; + qCDebug(modelformat) << "buildModelMesh failed -- no indices, url = " << url; return; } @@ -1505,7 +1505,7 @@ void buildModelMesh(ExtractedMesh& extracted) { mesh.setPartBuffer(pbv); } else { extracted.mesh._mesh = model::Mesh(); - qCDebug(modelformat) << "buildModelMesh failed -- no parts"; + qCDebug(modelformat) << "buildModelMesh failed -- no parts, url = " << url; return; } @@ -1530,7 +1530,7 @@ QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) { return filename; } -FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { QHash meshes; QHash modelIDsToNames; QHash meshIDsToMeshIndices; @@ -1615,7 +1615,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, #if defined(DEBUG_FBXREADER) int unknown = 0; #endif - FBXGeometry geometry; + FBXGeometry* geometryPtr = new FBXGeometry; + FBXGeometry& geometry = *geometryPtr; + float unitScaleFactor = 1.0f; glm::vec3 ambientColor; QString hifiGlobalNodeID; @@ -2680,7 +2682,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex); # if USE_MODEL_MESH - buildModelMesh(extracted); + buildModelMesh(extracted, url); # endif if (extracted.mesh.isEye) { @@ -2761,15 +2763,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } } - return geometry; + return geometryPtr; } -FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel); } -FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel); } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index b8a22b0b80..471a9c1777 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -272,10 +272,10 @@ Q_DECLARE_METATYPE(FBXGeometry) /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); #endif // hifi_FBXReader_h diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index b7ae948490..841fdcfad9 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -399,15 +399,16 @@ done: } -FBXGeometry OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) { +FBXGeometry* OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); return readOBJ(&buffer, mapping, nullptr); } -FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) { - FBXGeometry geometry; +FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) { + FBXGeometry* geometryPtr = new FBXGeometry(); + FBXGeometry& geometry = *geometryPtr; OBJTokenizer tokenizer(device); float scaleGuess = 1.0f; @@ -545,7 +546,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q qCDebug(modelformat) << "OBJ reader fail: " << e.what(); } - return geometry; + return geometryPtr; } diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 2e7b050b0a..df4c88553e 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -71,8 +71,8 @@ public: QHash materials; QNetworkReply* request(QUrl& url, bool isTest); - FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping); - FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url); + FBXGeometry* readOBJ(const QByteArray& model, const QVariantHash& mapping); + FBXGeometry* readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url); private: QUrl* _url = nullptr; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index e127380630..75028abe93 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -320,7 +320,6 @@ void Resource::attemptRequest() { void Resource::finishedLoading(bool success) { if (success) { _loaded = true; - emit loaded(); } else { _failedToLoad = true; } @@ -333,91 +332,26 @@ void Resource::reinsert() { static const int REPLY_TIMEOUT_MS = 5000; void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (!_reply->isFinished()) { - _bytesReceived = bytesReceived; - _bytesTotal = bytesTotal; - _replyTimer->start(REPLY_TIMEOUT_MS); - return; - } - _reply->disconnect(this); - _replyTimer->disconnect(this); - QNetworkReply* reply = _reply; - _reply = nullptr; - _replyTimer->deleteLater(); - _replyTimer = nullptr; - ResourceCache::requestCompleted(this); - - downloadFinished(reply); + _bytesReceived = bytesReceived; + _bytesTotal = bytesTotal; + _replyTimer->start(REPLY_TIMEOUT_MS); } void Resource::handleReplyError() { - handleReplyError(_reply->error(), qDebug() << _reply->errorString()); + handleReplyErrorInternal(_reply->error()); } void Resource::handleReplyTimeout() { - handleReplyError(QNetworkReply::TimeoutError, qDebug() << "Timed out loading" << _reply->url() << - "received" << _bytesReceived << "total" << _bytesTotal); -} - -void Resource::maybeRefresh() { - if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) { - QNetworkReply* reply = qobject_cast(sender()); - QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader); - QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url); - if (variant.isValid() && variant.canConvert() && metaData.isValid()) { - QDateTime lastModified = variant.value(); - QDateTime lastModifiedOld = metaData.lastModified(); - if (lastModified.isValid() && lastModifiedOld.isValid() && - lastModifiedOld >= lastModified) { // With >=, cache won't thrash in eventually-consistent cdn. - qCDebug(networking) << "Using cached version of" << _url.fileName(); - // We don't need to update, return - return; - } - } else if (!variant.isValid() || !variant.canConvert() || - !variant.value().isValid() || variant.value().isNull()) { - qCDebug(networking) << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated"; - return; - } - qCDebug(networking) << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing."; - refresh(); - } + handleReplyErrorInternal(QNetworkReply::TimeoutError); } void Resource::makeRequest() { _reply = NetworkAccessManager::getInstance().get(_request); - + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished())); - - if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) { - // If the file as been updated since it was cached, refresh it - QNetworkRequest request(_request); - request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); - request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); - QNetworkReply* reply = NetworkAccessManager::getInstance().head(request); - connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh); - } else { - if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) { - QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url); - bool needUpdate = false; - if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) { - // If the expiration date is NULL or in the past, - // put one far enough away that it won't be an issue. - metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100)); - needUpdate = true; - } - if (metaData.lastModified().isNull()) { - // If the lastModified date is NULL, set it to now. - metaData.setLastModified(QDateTime::currentDateTime()); - needUpdate = true; - } - if (needUpdate) { - NetworkAccessManager::getInstance().cache()->updateMetaData(metaData); - } - } - } - + _replyTimer = new QTimer(this); connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout())); _replyTimer->setSingleShot(true); @@ -425,7 +359,8 @@ void Resource::makeRequest() { _bytesReceived = _bytesTotal = 0; } -void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) { +void Resource::handleReplyErrorInternal(QNetworkReply::NetworkError error) { + _reply->disconnect(this); _replyTimer->disconnect(this); _reply->deleteLater(); @@ -433,7 +368,7 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) _replyTimer->deleteLater(); _replyTimer = nullptr; ResourceCache::requestCompleted(this); - + // retry for certain types of failures switch (error) { case QNetworkReply::RemoteHostClosedError: @@ -444,26 +379,46 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) case QNetworkReply::UnknownNetworkError: case QNetworkReply::UnknownProxyError: case QNetworkReply::UnknownContentError: - case QNetworkReply::ProtocolFailure: { + case QNetworkReply::ProtocolFailure: { // retry with increasing delays const int MAX_ATTEMPTS = 8; const int BASE_DELAY_MS = 1000; if (++_attempts < MAX_ATTEMPTS) { QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest())); - debug << "-- retrying..."; + qCWarning(networking) << "error downloading url =" << _url.toDisplayString() << ", error =" << error << ", retrying (" << _attempts << "/" << MAX_ATTEMPTS << ")"; return; } // fall through to final failure - } + } default: + qCCritical(networking) << "error downloading, url =" << _url.toDisplayString() << ", error =" << error; + emit failed(error); finishedLoading(false); break; } } void Resource::handleReplyFinished() { - qCDebug(networking) << "Got finished without download progress/error?" << _url; - handleDownloadProgress(0, 0); + + bool fromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); + qCDebug(networking) << "success downloading url =" << _url.toDisplayString() << (fromCache ? "from cache" : ""); + + _reply->disconnect(this); + _replyTimer->disconnect(this); + QNetworkReply* reply = _reply; + _reply = nullptr; + _replyTimer->deleteLater(); + _replyTimer = nullptr; + ResourceCache::requestCompleted(this); + + finishedLoading(true); + emit loaded(*reply); + downloadFinished(reply); +} + + +void Resource::downloadFinished(QNetworkReply* reply) { + ; } uint qHash(const QPointer& value, uint seed) { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 93ddfe77be..9a88c434e1 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -150,7 +150,7 @@ public: float getLoadPriority(); /// Checks whether the resource has loaded. - bool isLoaded() const { return _loaded; } + virtual bool isLoaded() const { return _loaded; } /// For loading resources, returns the number of bytes received. qint64 getBytesReceived() const { return _bytesReceived; } @@ -174,21 +174,22 @@ public: signals: /// Fired when the resource has been loaded. - void loaded(); + void loaded(QNetworkReply& request); + + /// Fired when resource failed to load. + void failed(QNetworkReply::NetworkError error); + + /// Fired when resource is refreshed. void onRefresh(); protected slots: void attemptRequest(); - - /// Refreshes the resource if the last modified date on the network - /// is greater than the last modified date in the cache. - void maybeRefresh(); protected: virtual void init(); /// Called when the download has finished. The recipient should delete the reply when done with it. - virtual void downloadFinished(QNetworkReply* reply) = 0; + virtual void downloadFinished(QNetworkReply* reply); /// Should be called by subclasses when all the loading that will be done has been done. Q_INVOKABLE void finishedLoading(bool success); @@ -216,7 +217,7 @@ private: void makeRequest(); - void handleReplyError(QNetworkReply::NetworkError error, QDebug debug); + void handleReplyErrorInternal(QNetworkReply::NetworkError error); friend class ResourceCache; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 31b030f75a..f48ceb9b62 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include @@ -50,6 +49,13 @@ GeometryCache::~GeometryCache() { #endif //def WANT_DEBUG } +QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra) { + // NetworkGeometry is no longer a subclass of Resource, but requires this method because, it is pure virtual. + assert(false); + return QSharedPointer(); +} + const int NUM_VERTICES_PER_TRIANGLE = 3; const int NUM_TRIANGLES_PER_QUAD = 2; const int NUM_VERTICES_PER_TRIANGULATED_QUAD = NUM_VERTICES_PER_TRIANGLE * NUM_TRIANGLES_PER_QUAD; @@ -1643,19 +1649,6 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm batch.draw(gpu::LINES, 2, 0); } - -QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) { - return getResource(url, fallback, delayLoad, NULL).staticCast(); -} - -QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, - bool delayLoad, const void* extra) { - QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad), - &Resource::allReferencesCleared); - geometry->setLODParent(geometry); - return geometry.staticCast(); -} - void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { if (!_standardDrawPipeline) { auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert))); @@ -1685,33 +1678,82 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { } } -const float NetworkGeometry::NO_HYSTERESIS = -1.0f; +GeometryReader::GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping) : + _url(url), + _reply(reply), + _mapping(mapping) { +} -NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, - const QVariantHash& mapping, const QUrl& textureBase) : - Resource(url, delayLoad), - _mapping(mapping), - _textureBase(textureBase.isValid() ? textureBase : url), - _fallback(fallback) -{ - - if (url.isEmpty()) { - // make the minimal amount of dummy geometry to satisfy Model - FBXJoint joint = { false, QVector(), -1, 0.0f, 0.0f, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), - glm::quat(), glm::mat4(), glm::mat4(), glm::vec3(), glm::vec3(), glm::quat(), glm::quat(), - glm::mat4(), QString(""), false}; - _geometry.joints.append(joint); - _geometry.leftEyeJointIndex = -1; - _geometry.rightEyeJointIndex = -1; - _geometry.neckJointIndex = -1; - _geometry.rootJointIndex = -1; - _geometry.leanJointIndex = -1; - _geometry.headJointIndex = -1; - _geometry.leftHandJointIndex = -1; - _geometry.rightHandJointIndex = -1; +void GeometryReader::run() { + try { + if (!_reply) { + throw QString("Reply is NULL ?!"); + } + QString urlname = _url.path().toLower(); + bool urlValid = true; + urlValid &= !urlname.isEmpty(); + urlValid &= !_url.path().isEmpty(); + urlValid &= _url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj"); + + if (urlValid) { + // Let's read the binaries from the network + FBXGeometry* fbxgeo = nullptr; + if (_url.path().toLower().endsWith(".fbx")) { + const bool grabLightmaps = true; + const float lightmapLevel = 1.0f; + fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel); + } else if (_url.path().toLower().endsWith(".obj")) { + fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url); + } else { + QString errorStr("usupported format"); + emit onError(NetworkGeometry::ModelParseError, errorStr); + } + emit onSuccess(fbxgeo); + } else { + throw QString("url is invalid"); + } + + } catch (const QString& error) { + qCDebug(renderutils) << "Error reading " << _url << ": " << error; + emit onError(NetworkGeometry::ModelParseError, error); } - - connect(this, &Resource::loaded, this, &NetworkGeometry::replaceTexturesWithPendingChanges); + _reply->deleteLater(); +} + +NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : + _url(url), + _mapping(mapping), + _textureBaseUrl(textureBaseUrl) { + + if (delayLoad) { + _state = DelayState; + } else { + attemptRequestInternal(); + } +} + +NetworkGeometry::~NetworkGeometry() { + if (_resource) { + _resource->deleteLater(); + } +} + +void NetworkGeometry::attemptRequest() { + if (_state == DelayState) { + attemptRequestInternal(); + } +} + +void NetworkGeometry::attemptRequestInternal() { + if (_url.path().toLower().endsWith(".fst")) { + requestMapping(_url); + } else { + requestModel(_url); + } +} + +bool NetworkGeometry::isLoaded() const { + return _state == SuccessState; } bool NetworkGeometry::isLoadedWithTextures() const { @@ -1719,12 +1761,12 @@ bool NetworkGeometry::isLoadedWithTextures() const { return false; } if (!_isLoadedWithTextures) { - foreach (const NetworkMesh& mesh, _meshes) { - foreach (const NetworkMeshPart& part, mesh.parts) { - if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) || - (part.normalTexture && !part.normalTexture->isLoaded()) || - (part.specularTexture && !part.specularTexture->isLoaded()) || - (part.emissiveTexture && !part.emissiveTexture->isLoaded())) { + for (auto&& mesh : _meshes) { + for (auto && part : mesh->_parts) { + if ((part->diffuseTexture && !part->diffuseTexture->isLoaded()) || + (part->normalTexture && !part->normalTexture->isLoaded()) || + (part->specularTexture && !part->specularTexture->isLoaded()) || + (part->emissiveTexture && !part->emissiveTexture->isLoaded())) { return false; } } @@ -1734,183 +1776,38 @@ bool NetworkGeometry::isLoadedWithTextures() const { return true; } -QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const { - if (_lodParent.data() != this) { - return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad); - } - if (_failedToLoad && _fallback) { - return _fallback; - } - QSharedPointer lod = _lodParent; - float lodDistance = 0.0f; - QMap >::const_iterator it = _lods.upperBound(distance); - if (it != _lods.constBegin()) { - it = it - 1; - lod = it.value(); - lodDistance = it.key(); - } - if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) { - // if we previously selected a different distance, make sure we've moved far enough to justify switching - const float HYSTERESIS_PROPORTION = 0.1f; - if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) { - lod = _lodParent; - lodDistance = 0.0f; - it = _lods.upperBound(hysteresis); - if (it != _lods.constBegin()) { - it = it - 1; - lod = it.value(); - lodDistance = it.key(); - } - } - } - if (lod && lod->isLoaded()) { - hysteresis = lodDistance; - return lod; - } - // if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one - if (!delayLoad) { - lod->ensureLoading(); - } - float closestDistance = FLT_MAX; - if (isLoaded()) { - lod = _lodParent; - closestDistance = distance; - } - for (it = _lods.constBegin(); it != _lods.constEnd(); it++) { - float distanceToLOD = glm::abs(distance - it.key()); - if (it.value()->isLoaded() && distanceToLOD < closestDistance) { - lod = it.value(); - closestDistance = distanceToLOD; - } - } - hysteresis = NO_HYSTERESIS; - return lod; -} - -uint qHash(const QWeakPointer& animation, uint seed = 0) { - return qHash(animation.data(), seed); -} - -QVector NetworkGeometry::getJointMappings(const AnimationPointer& animation) { - QVector mappings = _jointMappings.value(animation); - if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) { - const FBXGeometry& animationGeometry = animation->getGeometry(); - for (int i = 0; i < animationGeometry.joints.size(); i++) { - mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1); - } - _jointMappings.insert(animation, mappings); - } - return mappings; -} - -void NetworkGeometry::setLoadPriority(const QPointer& owner, float priority) { - Resource::setLoadPriority(owner, priority); - - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - if (part.diffuseTexture) { - part.diffuseTexture->setLoadPriority(owner, priority); - } - if (part.normalTexture) { - part.normalTexture->setLoadPriority(owner, priority); - } - if (part.specularTexture) { - part.specularTexture->setLoadPriority(owner, priority); - } - if (part.emissiveTexture) { - part.emissiveTexture->setLoadPriority(owner, priority); - } - } - } -} - -void NetworkGeometry::setLoadPriorities(const QHash, float>& priorities) { - Resource::setLoadPriorities(priorities); - - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - if (part.diffuseTexture) { - part.diffuseTexture->setLoadPriorities(priorities); - } - if (part.normalTexture) { - part.normalTexture->setLoadPriorities(priorities); - } - if (part.specularTexture) { - part.specularTexture->setLoadPriorities(priorities); - } - if (part.emissiveTexture) { - part.emissiveTexture->setLoadPriorities(priorities); - } - } - } -} - -void NetworkGeometry::clearLoadPriority(const QPointer& owner) { - Resource::clearLoadPriority(owner); - - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - if (part.diffuseTexture) { - part.diffuseTexture->clearLoadPriority(owner); - } - if (part.normalTexture) { - part.normalTexture->clearLoadPriority(owner); - } - if (part.specularTexture) { - part.specularTexture->clearLoadPriority(owner); - } - if (part.emissiveTexture) { - part.emissiveTexture->clearLoadPriority(owner); - } - } - } -} - void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) { if (_meshes.size() > 0) { auto textureCache = DependencyManager::get(); - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - + for (size_t i = 0; i < _meshes.size(); i++) { + NetworkMesh& mesh = *(_meshes[i].get()); + for (size_t j = 0; j < mesh._parts.size(); j++) { + NetworkMeshPart& part = *(mesh._parts[j].get()); QSharedPointer matchingTexture = QSharedPointer(); if (part.diffuseTextureName == name) { - part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry.meshes[i].isEye); - part.diffuseTexture->setLoadPriorities(_loadPriorities); + part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry->meshes[i].isEye); } else if (part.normalTextureName == name) { part.normalTexture = textureCache->getTexture(url); - part.normalTexture->setLoadPriorities(_loadPriorities); } else if (part.specularTextureName == name) { part.specularTexture = textureCache->getTexture(url); - part.specularTexture->setLoadPriorities(_loadPriorities); } else if (part.emissiveTextureName == name) { part.emissiveTexture = textureCache->getTexture(url); - part.emissiveTexture->setLoadPriorities(_loadPriorities); } } } } else { - qCDebug(renderutils) << "Adding a name url pair to pending" << name << url; - // we don't have meshes downloaded yet, so hold this texture as pending - _pendingTextureChanges.insert(name, url); + qCWarning(renderutils) << "Ignoring setTextureWirthNameToURL() geometry not ready." << name << url; } _isLoadedWithTextures = false; } QStringList NetworkGeometry::getTextureNames() const { QStringList result; - for (int i = 0; i < _meshes.size(); i++) { - const NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - const NetworkMeshPart& part = mesh.parts[j]; - + for (size_t i = 0; i < _meshes.size(); i++) { + const NetworkMesh& mesh = *(_meshes[i].get()); + for (size_t j = 0; j < mesh._parts.size(); j++) { + const NetworkMeshPart& part = *(mesh._parts[j].get()); + if (!part.diffuseTextureName.isEmpty() && part.diffuseTexture) { QString textureURL = part.diffuseTexture->getURL().toString(); result << part.diffuseTextureName + ":" + textureURL; @@ -1935,320 +1832,259 @@ QStringList NetworkGeometry::getTextureNames() const { return result; } -void NetworkGeometry::replaceTexturesWithPendingChanges() { - QHash::Iterator it = _pendingTextureChanges.begin(); - - while (it != _pendingTextureChanges.end()) { - setTextureWithNameToURL(it.key(), it.value()); - it = _pendingTextureChanges.erase(it); +void NetworkGeometry::requestMapping(const QUrl& url) { + _state = RequestMappingState; + if (_resource) { + _resource->deleteLater(); } + _resource = new Resource(url, false); + connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(mappingRequestDone(QNetworkReply&))); + connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(mappingRequestError(QNetworkReply::NetworkError))); } -/// Reads geometry in a worker thread. -class GeometryReader : public QRunnable { -public: - - GeometryReader(const QWeakPointer& geometry, const QUrl& url, - QNetworkReply* reply, const QVariantHash& mapping); - - virtual void run(); - -private: - - QWeakPointer _geometry; - QUrl _url; - QNetworkReply* _reply; - QVariantHash _mapping; -}; - -GeometryReader::GeometryReader(const QWeakPointer& geometry, const QUrl& url, - QNetworkReply* reply, const QVariantHash& mapping) : - _geometry(geometry), - _url(url), - _reply(reply), - _mapping(mapping) { +void NetworkGeometry::requestModel(const QUrl& url) { + _state = RequestModelState; + if (_resource) { + _resource->deleteLater(); + } + _resource = new Resource(url, false); + connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(modelRequestDone(QNetworkReply&))); + connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(modelRequestError(QNetworkReply::NetworkError))); } -void GeometryReader::run() { - QSharedPointer geometry = _geometry.toStrongRef(); - if (geometry.isNull()) { - _reply->deleteLater(); - return; - } - try { - if (!_reply) { - throw QString("Reply is NULL ?!"); - } - QString urlname = _url.path().toLower(); - bool urlValid = true; - urlValid &= !urlname.isEmpty(); - urlValid &= !_url.path().isEmpty(); - urlValid &= _url.path().toLower().endsWith(".fbx") - || _url.path().toLower().endsWith(".obj") - || _url.path().toLower().endsWith(".svo"); +void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) { + assert(_state == RequestMappingState); - if (urlValid) { - // Let's read the binaries from the network - FBXGeometry fbxgeo; - if (_url.path().toLower().endsWith(".fbx")) { - bool grabLightmaps = true; - float lightmapLevel = 1.0f; - // HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber... - if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) { - grabLightmaps = false; - } else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) { - lightmapLevel = 4.0f; - } else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) { - lightmapLevel = 3.5f; - } - fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel); - } else if (_url.path().toLower().endsWith(".obj")) { - fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url); + // parse the mapping file + _mapping = FSTReader::readMapping(reply.readAll()); + + QUrl replyUrl = reply.url(); + QString modelUrlStr = _mapping.value("filename").toString(); + if (modelUrlStr.isNull()) { + qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry"; + emit onFailure(*this, MissingFilenameInMapping); + } else { + // read _textureBase from mapping file, if present + QString texdir = _mapping.value("texdir").toString(); + if (!texdir.isNull()) { + if (!texdir.endsWith('/')) { + texdir += '/'; } - QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo)); - } else { - throw QString("url is invalid"); + _textureBaseUrl = replyUrl.resolved(texdir); } - } catch (const QString& error) { - qCDebug(renderutils) << "Error reading " << _url << ": " << error; - QMetaObject::invokeMethod(geometry.data(), "finishedLoading", Q_ARG(bool, false)); - } - _reply->deleteLater(); -} - -void NetworkGeometry::init() { - _mapping = QVariantHash(); - _geometry = FBXGeometry(); - _meshes.clear(); - _lods.clear(); - _pendingTextureChanges.clear(); - _request.setUrl(_url); - Resource::init(); -} - -void NetworkGeometry::downloadFinished(QNetworkReply* reply) { - QUrl url = reply->url(); - if (url.path().toLower().endsWith(".fst")) { - // it's a mapping file; parse it and get the mesh filename - _mapping = FSTReader::readMapping(reply->readAll()); - reply->deleteLater(); - QString filename = _mapping.value("filename").toString(); - if (filename.isNull()) { - qCDebug(renderutils) << "Mapping file " << url << " has no filename."; - finishedLoading(false); - - } else { - QString texdir = _mapping.value("texdir").toString(); - if (!texdir.isNull()) { - if (!texdir.endsWith('/')) { - texdir += '/'; - } - _textureBase = url.resolved(texdir); - } - QVariantHash lods = _mapping.value("lod").toHash(); - for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) { - auto geometry = QSharedPointer::create(url.resolved(it.key()), - QSharedPointer(), true, _mapping, _textureBase); - geometry->setSelf(geometry.staticCast()); - geometry->setLODParent(_lodParent); - _lods.insert(it.value().toFloat(), geometry); - } - _request.setUrl(url.resolved(filename)); - - // make the request immediately only if we have no LODs to switch between - _startedLoading = false; - if (_lods.isEmpty()) { - attemptRequest(); - } - } - return; - } - - // send the reader off to the thread pool - QThreadPool::globalInstance()->start(new GeometryReader(_self, url, reply, _mapping)); -} - -void NetworkGeometry::reinsert() { - Resource::reinsert(); - - _lodParent = qWeakPointerCast(_self); - foreach (const QSharedPointer& lod, _lods) { - lod->setLODParent(_lodParent); + QUrl modelUrl = replyUrl.resolved(modelUrlStr); + requestModel(modelUrl); } } -void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { - _geometry = geometry; +void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) { + assert(_state == RequestMappingState); + _state = ErrorState; + emit onFailure(*this, MappingRequestError); +} +void NetworkGeometry::modelRequestDone(QNetworkReply& reply) { + assert(_state == RequestModelState); + + _state = ParsingModelState; + + // asynchronously parse the model file. + GeometryReader* geometryReader = new GeometryReader(reply.url(), &reply, _mapping); + connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*))); + connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString))); + + QThreadPool::globalInstance()->start(geometryReader); +} + +void NetworkGeometry::modelRequestError(QNetworkReply::NetworkError error) { + assert(_state == RequestModelState); + _state = ErrorState; + emit onFailure(*this, ModelRequestError); +} + +static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBaseUrl) { auto textureCache = DependencyManager::get(); - - foreach (const FBXMesh& mesh, _geometry.meshes) { - NetworkMesh networkMesh; - - int totalIndices = 0; - bool checkForTexcoordLightmap = false; - foreach (const FBXMeshPart& part, mesh.parts) { - NetworkMeshPart networkPart; - if (!part.diffuseTexture.filename.isEmpty()) { - networkPart.diffuseTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE, - mesh.isEye, part.diffuseTexture.content); - networkPart.diffuseTextureName = part.diffuseTexture.name; - networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); - } - if (!part.normalTexture.filename.isEmpty()) { - networkPart.normalTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE, - false, part.normalTexture.content); - networkPart.normalTextureName = part.normalTexture.name; - networkPart.normalTexture->setLoadPriorities(_loadPriorities); - } - if (!part.specularTexture.filename.isEmpty()) { - networkPart.specularTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE, - false, part.specularTexture.content); - networkPart.specularTextureName = part.specularTexture.name; - networkPart.specularTexture->setLoadPriorities(_loadPriorities); - } - if (!part.emissiveTexture.filename.isEmpty()) { - networkPart.emissiveTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE, - false, part.emissiveTexture.content); - networkPart.emissiveTextureName = part.emissiveTexture.name; - networkPart.emissiveTexture->setLoadPriorities(_loadPriorities); - checkForTexcoordLightmap = true; - } - networkMesh.parts.append(networkPart); - - totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); + NetworkMesh* networkMesh = new NetworkMesh(); + + int totalIndices = 0; + bool checkForTexcoordLightmap = false; + + // process network parts + foreach (const FBXMeshPart& part, mesh.parts) { + NetworkMeshPart* networkPart = new NetworkMeshPart(); + + if (!part.diffuseTexture.filename.isEmpty()) { + networkPart->diffuseTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE, + mesh.isEye, part.diffuseTexture.content); + networkPart->diffuseTextureName = part.diffuseTexture.name; } - - { - networkMesh._indexBuffer = std::make_shared(); - networkMesh._indexBuffer->resize(totalIndices * sizeof(int)); - int offset = 0; - foreach(const FBXMeshPart& part, mesh.parts) { - networkMesh._indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int), - (gpu::Byte*) part.quadIndices.constData()); - offset += part.quadIndices.size() * sizeof(int); - networkMesh._indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int), - (gpu::Byte*) part.triangleIndices.constData()); - offset += part.triangleIndices.size() * sizeof(int); - } + if (!part.normalTexture.filename.isEmpty()) { + networkPart->normalTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE, + false, part.normalTexture.content); + networkPart->normalTextureName = part.normalTexture.name; } - - { - networkMesh._vertexBuffer = std::make_shared(); - // if we don't need to do any blending, the positions/normals can be static - if (mesh.blendshapes.isEmpty()) { - int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3); - int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3); - int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3); - int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); - int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); - int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2); - int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); - - networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); - - networkMesh._vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); - networkMesh._vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); - networkMesh._vertexBuffer->setSubData(tangentsOffset, - mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); - networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); - networkMesh._vertexBuffer->setSubData(texCoordsOffset, - mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); - networkMesh._vertexBuffer->setSubData(texCoords1Offset, - mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData()); - networkMesh._vertexBuffer->setSubData(clusterIndicesOffset, - mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); - networkMesh._vertexBuffer->setSubData(clusterWeightsOffset, - mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); - - // otherwise, at least the cluster indices/weights can be static - networkMesh._vertexStream = std::make_shared(); - networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3)); - if (mesh.normals.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, normalsOffset, sizeof(glm::vec3)); - if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, tangentsOffset, sizeof(glm::vec3)); - if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3)); - if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); - if (mesh.texCoords1.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoords1Offset, sizeof(glm::vec2)); - if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); - if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); - - int channelNum = 0; - networkMesh._vertexFormat = std::make_shared(); - networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); - if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); - if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - if (mesh.texCoords1.size()) { - networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - } else if (checkForTexcoordLightmap && mesh.texCoords.size()) { - // need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel - networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - } - if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - } - else { - int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); - int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); - int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); - int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); - - networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); - networkMesh._vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); - networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); - networkMesh._vertexBuffer->setSubData(texCoordsOffset, - mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); - networkMesh._vertexBuffer->setSubData(clusterIndicesOffset, - mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); - networkMesh._vertexBuffer->setSubData(clusterWeightsOffset, - mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); - - networkMesh._vertexStream = std::make_shared(); - if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3)); - if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3)); - if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); - if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); - if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); - - int channelNum = 0; - networkMesh._vertexFormat = std::make_shared(); - networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); - if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - - } + if (!part.specularTexture.filename.isEmpty()) { + networkPart->specularTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE, + false, part.specularTexture.content); + networkPart->specularTextureName = part.specularTexture.name; } - - _meshes.append(networkMesh); + if (!part.emissiveTexture.filename.isEmpty()) { + networkPart->emissiveTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE, + false, part.emissiveTexture.content); + networkPart->emissiveTextureName = part.emissiveTexture.name; + checkForTexcoordLightmap = true; + } + networkMesh->_parts.emplace_back(networkPart); + totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); } - - finishedLoading(true); + + // initialize index buffer + { + networkMesh->_indexBuffer = std::make_shared(); + networkMesh->_indexBuffer->resize(totalIndices * sizeof(int)); + int offset = 0; + foreach(const FBXMeshPart& part, mesh.parts) { + networkMesh->_indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int), + (gpu::Byte*) part.quadIndices.constData()); + offset += part.quadIndices.size() * sizeof(int); + networkMesh->_indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int), + (gpu::Byte*) part.triangleIndices.constData()); + offset += part.triangleIndices.size() * sizeof(int); + } + } + + // initialize vertex buffer + { + networkMesh->_vertexBuffer = std::make_shared(); + // if we don't need to do any blending, the positions/normals can be static + if (mesh.blendshapes.isEmpty()) { + int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3); + int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3); + int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3); + int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); + int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); + int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2); + int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); + + networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); + + networkMesh->_vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); + networkMesh->_vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); + networkMesh->_vertexBuffer->setSubData(tangentsOffset, + mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); + networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); + networkMesh->_vertexBuffer->setSubData(texCoordsOffset, + mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); + networkMesh->_vertexBuffer->setSubData(texCoords1Offset, + mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData()); + networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset, + mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); + networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset, + mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); + + // otherwise, at least the cluster indices/weights can be static + networkMesh->_vertexStream = std::make_shared(); + networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3)); + if (mesh.normals.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, normalsOffset, sizeof(glm::vec3)); + if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, tangentsOffset, sizeof(glm::vec3)); + if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3)); + if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); + if (mesh.texCoords1.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoords1Offset, sizeof(glm::vec2)); + if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); + if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); + + int channelNum = 0; + networkMesh->_vertexFormat = std::make_shared(); + networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); + if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + if (mesh.texCoords1.size()) { + networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + } else if (checkForTexcoordLightmap && mesh.texCoords.size()) { + // need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel + networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + } + if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + } + else { + int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); + int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); + int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); + int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); + + networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); + networkMesh->_vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); + networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); + networkMesh->_vertexBuffer->setSubData(texCoordsOffset, + mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); + networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset, + mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); + networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset, + mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); + + networkMesh->_vertexStream = std::make_shared(); + if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3)); + if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3)); + if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); + if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); + if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); + + int channelNum = 0; + networkMesh->_vertexFormat = std::make_shared(); + networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); + if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + } + } + + return networkMesh; +} + +void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) { + // assume owner ship of geometry pointer + _geometry.reset(geometry); + + foreach(const FBXMesh& mesh, _geometry->meshes) { + _meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl)); + } + + _state = SuccessState; + emit onSuccess(*this, *_geometry.get()); + + delete _resource; + _resource = nullptr; +} + +void NetworkGeometry::modelParseError(int error, QString str) { + _state = ErrorState; + emit onFailure(*this, (NetworkGeometry::Error)error); + + delete _resource; + _resource = nullptr; } bool NetworkMeshPart::isTranslucent() const { return diffuseTexture && diffuseTexture->isTranslucent(); } - bool NetworkMesh::isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const { assert(partIndex >= 0); - assert(partIndex < parts.size()); - return (parts.at(partIndex).isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f); + assert((size_t)partIndex < _parts.size()); + return (_parts.at(partIndex)->isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f); } int NetworkMesh::getTranslucentPartCount(const FBXMesh& fbxMesh) const { int count = 0; - for (int i = 0; i < parts.size(); i++) { + + for (size_t i = 0; i < _parts.size(); i++) { if (isPartTranslucent(fbxMesh, i)) { count++; } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index f70eae6380..774df1561c 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -13,6 +13,7 @@ #define hifi_GeometryCache_h #include +#include #include #include @@ -129,6 +130,9 @@ public: int allocateID() { return _nextID++; } static const int UNKNOWN_ID; + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra); + void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec3& color, bool solid = true, int id = UNKNOWN_ID) { renderSphere(batch, radius, slices, stacks, glm::vec4(color, 1.0f), solid, id); } @@ -208,11 +212,6 @@ public: /// Set a batch to the simple pipeline, returning the previous pipeline void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false); -protected: - - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); - private: GeometryCache(); virtual ~GeometryCache(); @@ -305,70 +304,104 @@ private: QHash > _networkGeometry; }; -/// Geometry loaded from the network. -class NetworkGeometry : public Resource { +class NetworkGeometry : public QObject { Q_OBJECT public: - - /// A hysteresis value indicating that we have no state memory. - static const float NO_HYSTERESIS; - - NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, - const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); + // mapping is only used if url is a .fbx or .obj file, it is essentially the content of an fst file. + // if delayLoad is true, the url will not be immediately downloaded. + // use the attemptRequest method to initiate the download. + NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl = QUrl()); + ~NetworkGeometry(); - /// Checks whether the geometry and its textures are loaded. + const QUrl& getURL() const { return _url; } + + void attemptRequest(); + + // true when the geometry is loaded (but maybe not it's associated textures) + bool isLoaded() const; + + // true when the requested geometry and its textures are loaded. bool isLoadedWithTextures() const; - /// Returns a pointer to the geometry appropriate for the specified distance. - /// \param hysteresis a hysteresis parameter that prevents rapid model switching - QSharedPointer getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const; + // WARNING: only valid when isLoaded returns true. + const FBXGeometry& getFBXGeometry() const { return *_geometry; } + const std::vector>& getMeshes() const { return _meshes; } - const FBXGeometry& getFBXGeometry() const { return _geometry; } - const QVector& getMeshes() const { return _meshes; } - - QVector getJointMappings(const AnimationPointer& animation); - - virtual void setLoadPriority(const QPointer& owner, float priority); - virtual void setLoadPriorities(const QHash, float>& priorities); - virtual void clearLoadPriority(const QPointer& owner); - void setTextureWithNameToURL(const QString& name, const QUrl& url); QStringList getTextureNames() const; - + + enum Error { + MissingFilenameInMapping = 0, + MappingRequestError, + ModelRequestError, + ModelParseError + }; + +signals: + // Fired when everything has downloaded and parsed successfully. + void onSuccess(NetworkGeometry& networkGeometry, FBXGeometry& fbxGeometry); + + // Fired when something went wrong. + void onFailure(NetworkGeometry& networkGeometry, Error error); + +protected slots: + void mappingRequestDone(QNetworkReply& reply); + void mappingRequestError(QNetworkReply::NetworkError error); + + void modelRequestDone(QNetworkReply& reply); + void modelRequestError(QNetworkReply::NetworkError error); + + void modelParseSuccess(FBXGeometry* geometry); + void modelParseError(int error, QString str); + protected: + void attemptRequestInternal(); + void requestMapping(const QUrl& url); + void requestModel(const QUrl& url); - virtual void init(); - virtual void downloadFinished(QNetworkReply* reply); - virtual void reinsert(); - - Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); - -private slots: - void replaceTexturesWithPendingChanges(); -private: - - friend class GeometryCache; - - void setLODParent(const QWeakPointer& lodParent) { _lodParent = lodParent; } - + enum State { DelayState, + RequestMappingState, + RequestModelState, + ParsingModelState, + SuccessState, + ErrorState }; + State _state; + + QUrl _url; QVariantHash _mapping; - QUrl _textureBase; - QSharedPointer _fallback; - - QMap > _lods; - FBXGeometry _geometry; - QVector _meshes; - - QWeakPointer _lodParent; - - QHash, QVector > _jointMappings; - - QHash _pendingTextureChanges; + QUrl _textureBaseUrl; + Resource* _resource = nullptr; + std::unique_ptr _geometry; + std::vector> _meshes; + + // cache for isLoadedWithTextures() mutable bool _isLoadedWithTextures = false; }; +/// Reads geometry in a worker thread. +class GeometryReader : public QObject, public QRunnable { + Q_OBJECT + +public: + + GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping); + + virtual void run(); + +signals: + void onSuccess(FBXGeometry* geometry); + void onError(int error, QString str); + +private: + + QWeakPointer _geometry; + QUrl _url; + QNetworkReply* _reply; + QVariantHash _mapping; +}; + /// The state associated with a single mesh part. class NetworkMeshPart { public: @@ -394,9 +427,9 @@ public: gpu::BufferStreamPointer _vertexStream; gpu::Stream::FormatPointer _vertexFormat; - - QVector parts; - + + std::vector> _parts; + int getTranslucentPartCount(const FBXMesh& fbxMesh) const; bool isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const; }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 7452c32ed2..c2d723a323 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -48,6 +48,8 @@ #include "model_lightmap_specular_map_frag.h" #include "model_translucent_frag.h" +#include "RenderUtilsLogging.h" + using namespace std; static int modelPointerTypeId = qRegisterMetaType >(); @@ -66,7 +68,6 @@ Model::Model(RigPointer rig, QObject* parent) : _snappedToRegistrationPoint(false), _showTrueJointTransforms(true), _cauterizeBones(false), - _lodDistance(0.0f), _pupilDilation(0.0f), _url(HTTP_INVALID_COM), _urlAsString(HTTP_INVALID_COM), @@ -234,7 +235,7 @@ QVector Model::createJointStates(const FBXGeometry& geometry) { }; void Model::initJointTransforms() { - if (!_geometry) { + if (!_geometry || !_geometry->isLoaded()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -368,8 +369,10 @@ void Model::init() { } void Model::reset() { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - _rig->reset(geometry.joints); + if (_geometry && _geometry->isLoaded()) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + _rig->reset(geometry.joints); + } _meshGroupsKnown = false; _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid @@ -378,68 +381,23 @@ void Model::reset() { bool Model::updateGeometry() { PROFILE_RANGE(__FUNCTION__); bool needFullUpdate = false; - bool needToRebuild = false; - if (_nextGeometry) { - _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); - _nextGeometry->setLoadPriority(this, -_lodDistance); - _nextGeometry->ensureLoading(); - if (_nextGeometry->isLoaded()) { - applyNextGeometry(); - needToRebuild = true; - } - } - if (!_geometry) { + + if (!_geometry || !_geometry->isLoaded()) { // geometry is not ready return false; } - QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); - if (_geometry != geometry) { + _needsReload = false; - // NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above. - // Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry. - - const FBXGeometry& newGeometry = geometry->getFBXGeometry(); - QVector newJointStates = createJointStates(newGeometry); - - if (! _rig->jointStatesEmpty()) { - // copy the existing joint states - const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); - for (QHash::const_iterator it = oldGeometry.jointIndices.constBegin(); - it != oldGeometry.jointIndices.constEnd(); it++) { - int oldIndex = it.value() - 1; - int newIndex = newGeometry.getJointIndex(it.key()); - if (newIndex != -1) { - newJointStates[newIndex].copyState(_rig->getJointState(oldIndex)); - } - } - } - - deleteGeometry(); - _dilatedTextures.clear(); - if (!geometry) { - std::cout << "WARNING: no geometry in Model::updateGeometry\n"; - } - setGeometry(geometry); - - _meshGroupsKnown = false; - _readyWhenAdded = false; // in case any of our users are using scenes - invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid - initJointStates(newJointStates); - needToRebuild = true; - } else if (_rig->jointStatesEmpty()) { + QSharedPointer geometry = _geometry; + if (_rig->jointStatesEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { initJointStates(createJointStates(fbxGeometry)); needToRebuild = true; } - } else if (!geometry->isLoaded()) { - deleteGeometry(); - _dilatedTextures.clear(); } - _geometry->setLoadPriority(this, -_lodDistance); - _geometry->ensureLoading(); if (needToRebuild) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); @@ -454,7 +412,7 @@ bool Model::updateGeometry() { buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); + mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); } _blendedVertexBuffers.push_back(buffer); } @@ -1069,53 +1027,36 @@ int Model::getLastFreeJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; } -void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) { +void Model::setURL(const QUrl& url) { + // don't recreate the geometry if it's the same URL if (_url == url && _geometry && _geometry->getURL() == url) { return; } - _readyWhenAdded = false; // reset out render items. - _needsReload = true; - invalidCalculatedMeshBoxes(); - _url = url; _urlAsString = _url.toString(); + { + render::PendingChanges pendingChanges; + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + removeFromScene(scene, pendingChanges); + scene->enqueuePendingChanges(pendingChanges); + } + + _needsReload = true; + _meshGroupsKnown = false; + invalidCalculatedMeshBoxes(); + deleteGeometry(); + + _geometry.reset(new NetworkGeometry(url, false, QVariantHash())); onInvalidate(); - - // if so instructed, keep the current geometry until the new one is loaded - _nextGeometry = DependencyManager::get()->getGeometry(url, fallback, delayLoad); - _nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS; - if (!retainCurrent || !isActive() || (_nextGeometry && _nextGeometry->isLoaded())) { - applyNextGeometry(); - } } -void Model::geometryRefreshed() { - QObject* sender = QObject::sender(); - - if (sender == _geometry) { - _readyWhenAdded = false; // reset out render items. - _needsReload = true; - invalidCalculatedMeshBoxes(); - - onInvalidate(); - - // if so instructed, keep the current geometry until the new one is loaded - _nextGeometry = DependencyManager::get()->getGeometry(_url); - _nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS; - applyNextGeometry(); - } else { - sender->disconnect(this, SLOT(geometryRefreshed())); - } -} - - const QSharedPointer Model::getCollisionGeometry(bool delayLoad) { if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) { - _collisionGeometry = DependencyManager::get()->getGeometry(_collisionUrl, QUrl(), delayLoad); + _collisionGeometry.reset(new NetworkGeometry(_collisionUrl, delayLoad, QVariantHash())); } if (_collisionGeometry && _collisionGeometry->isLoaded()) { @@ -1130,7 +1071,7 @@ void Model::setCollisionModelURL(const QUrl& url) { return; } _collisionUrl = url; - _collisionGeometry = DependencyManager::get()->getGeometry(url, QUrl(), true); + _collisionGeometry.reset(new NetworkGeometry(url, false, QVariantHash())); } bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { @@ -1461,46 +1402,23 @@ void Model::setGeometry(const QSharedPointer& newGeometry) { if (_geometry == newGeometry) { return; } - - if (_geometry) { - _geometry->disconnect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed); - } _geometry = newGeometry; - QObject::connect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed); -} - -void Model::applyNextGeometry() { - // delete our local geometry and custom textures - deleteGeometry(); - _dilatedTextures.clear(); - _lodHysteresis = _nextLODHysteresis; - - // we retain a reference to the base geometry so that its reference count doesn't fall to zero - setGeometry(_nextGeometry); - - _meshGroupsKnown = false; - _readyWhenAdded = false; // in case any of our users are using scenes - _needsReload = false; // we are loaded now! - invalidCalculatedMeshBoxes(); - _nextGeometry.reset(); } void Model::deleteGeometry() { _blendedVertexBuffers.clear(); _rig->clearJointStates(); _meshStates.clear(); - _rig->deleteAnimations(); - - if (_geometry) { - _geometry->clearLoadPriority(this); - } - _blendedBlendshapeCoefficients.clear(); } AABox Model::getPartBounds(int meshIndex, int partIndex) { + if (!_geometry || !_geometry->isLoaded()) { + return AABox(); + } + if (meshIndex < _meshStates.size()) { const MeshState& state = _meshStates.at(meshIndex); bool isSkinned = state.clusterMatrices.size() > 1; @@ -1531,7 +1449,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { } void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) { - // PROFILE_RANGE(__FUNCTION__); +// PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("Model::renderPart"); if (!_readyWhenAdded) { return; // bail asap @@ -1557,14 +1475,14 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran auto alphaThreshold = args->_alphaThreshold; //translucent ? TRANSPARENT_ALPHA_THRESHOLD : OPAQUE_ALPHA_THRESHOLD; // FIX ME const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const QVector& networkMeshes = _geometry->getMeshes(); + const std::vector>& networkMeshes = _geometry->getMeshes(); // guard against partially loaded meshes - if (meshIndex >= networkMeshes.size() || meshIndex >= geometry.meshes.size() || meshIndex >= _meshStates.size() ) { + if (meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size() ) { return; } - const NetworkMesh& networkMesh = networkMeshes.at(meshIndex); + const NetworkMesh& networkMesh = *(networkMeshes.at(meshIndex).get()); const FBXMesh& mesh = geometry.meshes.at(meshIndex); const MeshState& state = _meshStates.at(meshIndex); @@ -1614,8 +1532,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. - - if (meshIndex < 0 || meshIndex >= networkMeshes.size() || meshIndex > geometry.meshes.size()) { + if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex > geometry.meshes.size()) { _meshGroupsKnown = false; // regenerate these lists next time around. _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid @@ -1669,11 +1586,11 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran } // guard against partially loaded meshes - if (partIndex >= networkMesh.parts.size() || partIndex >= mesh.parts.size()) { + if (partIndex >= (int)networkMesh._parts.size() || partIndex >= mesh.parts.size()) { return; } - const NetworkMeshPart& networkPart = networkMesh.parts.at(partIndex); + const NetworkMeshPart& networkPart = *(networkMesh._parts.at(partIndex).get()); const FBXMeshPart& part = mesh.parts.at(partIndex); model::MaterialPointer material = part._material; @@ -1790,10 +1707,10 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran void Model::segregateMeshGroups() { const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const QVector& networkMeshes = _geometry->getMeshes(); + const std::vector>& networkMeshes = _geometry->getMeshes(); // all of our mesh vectors must match in size - if (networkMeshes.size() != geometry.meshes.size() || + if ((int)networkMeshes.size() != geometry.meshes.size() || geometry.meshes.size() != _meshStates.size()) { qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; return; @@ -1803,12 +1720,12 @@ void Model::segregateMeshGroups() { _opaqueRenderItems.clear(); // Run through all of the meshes, and place them into their segregated, but unsorted buckets - for (int i = 0; i < networkMeshes.size(); i++) { - const NetworkMesh& networkMesh = networkMeshes.at(i); + for (int i = 0; i < (int)networkMeshes.size(); i++) { + const NetworkMesh& networkMesh = *(networkMeshes.at(i).get()); const FBXMesh& mesh = geometry.meshes.at(i); const MeshState& state = _meshStates.at(i); - bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size(); + bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == (int)networkMesh._parts.size(); bool hasTangents = !mesh.tangents.isEmpty(); bool hasSpecular = mesh.hasSpecularTexture(); bool hasLightmap = mesh.hasEmissiveTexture(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index c9b63b598e..e55bff6aca 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -68,11 +68,7 @@ public: /// Sets the URL of the model to render. - /// \param fallback the URL of a fallback model to render if the requested model fails to load - /// \param retainCurrent if true, keep rendering the current model until the new one is loaded - /// \param delayLoad if true, don't load the model immediately; wait until actually requested - Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(), - bool retainCurrent = false, bool delayLoad = false); + Q_INVOKABLE void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } const QString& getURLAsString() const { return _urlAsString; } @@ -89,7 +85,7 @@ public: render::Item::Status::Getters& statusGetters); void removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); void renderSetup(RenderArgs* args); - bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); } + bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); } bool isVisible() const { return _isVisible; } @@ -141,14 +137,11 @@ public: const QUrl& getCollisionURL() const { return _collisionUrl; } /// Returns a reference to the shared collision geometry. - const QSharedPointer getCollisionGeometry(bool delayLoad = true); + const QSharedPointer getCollisionGeometry(bool delayLoad = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } - /// Sets the distance parameter used for LOD computations. - void setLODDistance(float distance) { _lodDistance = distance; } - void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false); bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled @@ -309,20 +302,12 @@ protected: // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; - void geometryRefreshed(); - private: - void applyNextGeometry(); void deleteGeometry(); QVector createJointStates(const FBXGeometry& geometry); void initJointTransforms(); - QSharedPointer _nextGeometry; - float _lodDistance; - float _lodHysteresis; - float _nextLODHysteresis; - QSharedPointer _collisionGeometry; float _pupilDilation; diff --git a/tests/networking/src/ResourceTests.cpp b/tests/networking/src/ResourceTests.cpp new file mode 100644 index 0000000000..f18f676398 --- /dev/null +++ b/tests/networking/src/ResourceTests.cpp @@ -0,0 +1,95 @@ +// +// ResoruceTests.cpp +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ResourceCache.h" +#include "NetworkAccessManager.h" +#include "DependencyManager.h" + +#include "ResourceTests.h" + +QTEST_MAIN(ResourceTests) + +void ResourceTests::initTestCase() { + + auto resourceCacheSharedItems = DependencyManager::set(); + + const qint64 MAXIMUM_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB + + // set up the file cache + //QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + QString cachePath = "./resourceTestCache"; + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkDiskCache* cache = new QNetworkDiskCache(); + cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); + cache->setCacheDirectory(cachePath); + cache->clear(); // clear the cache + networkAccessManager.setCache(cache); +} + +static Resource* resource = nullptr; + + +static bool waitForSignal(QObject *sender, const char *signal, int timeout = 1000) { + QEventLoop loop; + QTimer timer; + timer.setInterval(timeout); + timer.setSingleShot(true); + + loop.connect(sender, signal, SLOT(quit())); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + timer.start(); + loop.exec(); + + return timer.isActive(); +} + +void ResourceTests::downloadFirst() { + + // download the Mery fst file + QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst"); + resource = new Resource(meryUrl, false); + + const int timeout = 1000; + QEventLoop loop; + QTimer timer; + timer.setInterval(timeout); + timer.setSingleShot(true); + + loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit())); + loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit())); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + timer.start(); + loop.exec(); + + QVERIFY(resource->isLoaded()); +} + +void ResourceTests::downloadAgain() { + + // download the Mery fst file + QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst"); + resource = new Resource(meryUrl, false); + + const int timeout = 1000; + QEventLoop loop; + QTimer timer; + timer.setInterval(timeout); + timer.setSingleShot(true); + + loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit())); + loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit())); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + timer.start(); + loop.exec(); + + QVERIFY(resource->isLoaded()); + +} diff --git a/tests/networking/src/ResourceTests.h b/tests/networking/src/ResourceTests.h new file mode 100644 index 0000000000..32fc151982 --- /dev/null +++ b/tests/networking/src/ResourceTests.h @@ -0,0 +1,23 @@ +// +// ResourceTests.h +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ResourceTests_h +#define hifi_ResourceTests_h + +#include + +class ResourceTests : public QObject { + Q_OBJECT +private slots: + void initTestCase(); + void downloadFirst(); + void downloadAgain(); +}; + +#endif // hifi_ResourceTests_h diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index 3c02c956e4..f39bea9cf9 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -36,15 +36,16 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { std::cout << "Reading FBX.....\n"; QByteArray fbxContents = fbx.readAll(); - + FBXGeometry* geom; if (filename.toLower().endsWith(".obj")) { - result = OBJReader().readOBJ(fbxContents, QVariantHash()); + geom = OBJReader().readOBJ(fbxContents, QVariantHash()); } else if (filename.toLower().endsWith(".fbx")) { - result = readFBX(fbxContents, QVariantHash(), filename); + geom = readFBX(fbxContents, QVariantHash(), filename); } else { qDebug() << "unknown file extension"; return false; } + result = *geom; reSortFBXGeometryMeshes(result); From 9f501d4d72ccb0274798e0786905bd237ccb5ec0 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 20 Aug 2015 16:55:19 -0700 Subject: [PATCH 098/170] first cut at attempting to hide menu in full screen --- interface/src/Application.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a28435e258..0bc4f9b943 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4985,6 +4985,12 @@ void Application::setFullscreen(const QScreen* target) { #endif _window->windowHandle()->setScreen((QScreen*)target); _window->showFullScreen(); + + // also hide the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + menuBar->setVisible(false); + } } void Application::unsetFullscreen(const QScreen* avoid) { @@ -5015,6 +5021,12 @@ void Application::unsetFullscreen(const QScreen* avoid) { #else _window->setGeometry(targetGeometry); #endif + + // also show the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + menuBar->setVisible(true); + } } From 06b2a88fb6f730c7fd157f9543671c2c39efc373 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 20 Aug 2015 19:11:15 -0700 Subject: [PATCH 099/170] toggle menu near top of window --- interface/src/Application.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0bc4f9b943..a088c42f87 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1744,6 +1744,27 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { return; } +#if 1 //ndef Q_OS_MAC + // If in full screen, and our main windows menu bar is hidden, and we're close to the top of the QMainWindow + // then show the menubar. + if (_window->isFullScreen()) { + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + static const int MENU_TOGGLE_AREA = 10; + if (!menuBar->isVisible()) { + if (event->pos().y() <= MENU_TOGGLE_AREA) { + menuBar->setVisible(true); + } + } else { + if (event->pos().y() > MENU_TOGGLE_AREA) { + menuBar->setVisible(false); + } + } + } + } +#endif + + _entities.mouseMoveEvent(event, deviceID); _controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts @@ -4986,11 +5007,13 @@ void Application::setFullscreen(const QScreen* target) { _window->windowHandle()->setScreen((QScreen*)target); _window->showFullScreen(); +#ifndef Q_OS_MAC // also hide the QMainWindow's menuBar QMenuBar* menuBar = _window->menuBar(); if (menuBar) { menuBar->setVisible(false); } +#endif } void Application::unsetFullscreen(const QScreen* avoid) { @@ -5022,11 +5045,13 @@ void Application::unsetFullscreen(const QScreen* avoid) { _window->setGeometry(targetGeometry); #endif +#ifndef Q_OS_MAC // also show the QMainWindow's menuBar QMenuBar* menuBar = _window->menuBar(); if (menuBar) { menuBar->setVisible(true); } +#endif } From bd29fb247497856f688f699780904eef1958c41f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 20 Aug 2015 19:31:51 -0700 Subject: [PATCH 100/170] fix ifdef --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a088c42f87..b9bafbab1a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1744,7 +1744,7 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { return; } -#if 1 //ndef Q_OS_MAC +#ifndef Q_OS_MAC // If in full screen, and our main windows menu bar is hidden, and we're close to the top of the QMainWindow // then show the menubar. if (_window->isFullScreen()) { From ef4ad3107e1c95f58208e470948070050412b35d Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Thu, 20 Aug 2015 22:11:11 -0700 Subject: [PATCH 101/170] First pass, grenade toy. Needs art. --- examples/toys/grenade.js | 197 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 examples/toys/grenade.js diff --git a/examples/toys/grenade.js b/examples/toys/grenade.js new file mode 100644 index 0000000000..545bfbb982 --- /dev/null +++ b/examples/toys/grenade.js @@ -0,0 +1,197 @@ +// +// Grenade.js +// examples +// +// Created by Philip Rosedale on August 20, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var grenadeURL = HIFI_PUBLIC_BUCKET + "models/props/grenade/grenade.fbx"; +var fuseSoundURL = HIFI_PUBLIC_BUCKET + "sounds/burningFuse.wav"; +var boomSoundURL = HIFI_PUBLIC_BUCKET + "sounds/explosion.wav"; + +var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0); +var audioOptions = { + volume: 0.5, + loop: true +} + +var injector = null; + +var fuseSound = SoundCache.getSound(fuseSoundURL, audioOptions.isStereo); +var boomSound = SoundCache.getSound(boomSoundURL, audioOptions.isStereo); + +var grenade = null; +var particles = null; +var properties = null; +var originalPosition = null; +var isGrenade = false; +var isBurning = false; + +var animationSettings = JSON.stringify({ + running: true, + loop: true + }); +var explodeAnimationSettings = JSON.stringify({ + running: true, + loop: false + }); + +var GRAVITY = -9.8; +var TIME_TO_EXPLODE = 2500; +var DISTANCE_IN_FRONT_OF_ME = 1.0; + +function makeGrenade() { + var position = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, + { x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME })); + var rotation = Quat.multiply(MyAvatar.orientation, + Quat.fromPitchYawRollDegrees(0, -90, 0)); + grenade = Entities.addEntity({ + type: "Model", + position: position, + rotation: rotation, + dimensions: { x: 0.09, + y: 0.20, + z: 0.09 }, + collisionsWillMove: true, + modelURL: grenadeURL, + shapeType: "box" + }); + + properties = Entities.getEntityProperties(grenade); + audioOptions.position = position; + audioOptions.orientation = rotation; + originalPosition = position; +} + +function update() { + if (!grenade) { + makeGrenade(); + } else { + var newProperties = Entities.getEntityProperties(grenade); + if (!isBurning) { + // If moved, start fuse + var FUSE_START_MOVE = 0.01; + if (Vec3.length(Vec3.subtract(newProperties.position, originalPosition)) > FUSE_START_MOVE) { + isBurning = true; + // Create fuse particles + particles = Entities.addEntity({ + type: "ParticleEffect", + animationSettings: animationSettings, + position: newProperties.position, + textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png', + emitRate: 100, + emitStrength: 2.0, + emitDirection: { x: 0.0, y: 1.0, z: 0.0 }, + color: { red: 200, green: 0, blue: 0 }, + lifespan: 10.0, + visible: true, + locked: false + + }); + // Start fuse sound + injector = Audio.playSound(fuseSound, audioOptions); + // Start explosion timer + Script.setTimeout(boom, TIME_TO_EXPLODE); + originalPosition = newProperties.position; + // Add gravity + Entities.editEntity(grenade, { gravity: {x: 0, y: GRAVITY, z: 0 }}); + } + } + + if (newProperties.type === "Model") { + if (newProperties.position != properties.position) { + audioOptions.position = newProperties.position; + } + if (newProperties.orientation != properties.orientation) { + audioOptions.orientation = newProperties.orientation; + } + + properties = newProperties; + // Update sound location if playing + if (injector) { + injector.options = audioOptions; + } + if (particles) { + Entities.editEntity(particles, { position: newProperties.position }); + } + } else { + grenade = null; + Script.update.disconnect(update); + Script.scriptEnding.connect(scriptEnding); + scriptEnding(); + Script.stop(); + } + } +} +function boom() { + injector.stop(); + isBurning = false; + var audioOptions = { + position: properties.position, + volume: 0.75, + loop: false + } + Audio.playSound(boomSound, audioOptions); + Entities.addEntity({ + type: "ParticleEffect", + animationSettings: explodeAnimationSettings, + position: properties.position, + textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png', + emitRate: 200, + emitStrength: 3.0, + emitDirection: { x: 0.0, y: 1.0, z: 0.0 }, + color: { red: 255, green: 255, blue: 0 }, + lifespan: 2.0, + visible: true, + lifetime: 2, + locked: false + + }); + var BLAST_RADIUS = 20.0; + var LIFT_DEPTH = 2.0; + var epicenter = properties.position; + epicenter.y -= LIFT_DEPTH; + blowShitUp(epicenter, BLAST_RADIUS); + deleteStuff(); +} + +function blowShitUp(position, radius) { + var stuff = Entities.findEntities(position, radius); + var numMoveable = 0; + var STRENGTH = 3.5; + for (var i = 0; i < stuff.length; i++) { + var properties = Entities.getEntityProperties(stuff[i]); + if (properties.collisionsWillMove) { + var diff = Vec3.subtract(properties.position, position); + var distance = Vec3.length(diff); + var velocity = Vec3.sum(properties.velocity, Vec3.multiply(STRENGTH * 1.0 / distance, Vec3.normalize(diff))); + Entities.editEntity(stuff[i], { velocity: velocity }); + } + } +} + +function scriptEnding() { + deleteStuff(); +} + +function deleteStuff() { + if (grenade != null) { + Entities.deleteEntity(grenade); + grenade = null; + } + if (particles != null) { + Entities.deleteEntity(particles); + particles = null; + } +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + From 7284e3aa3c42e4b188672531411e5f7d3d512f46 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Thu, 20 Aug 2015 22:20:51 -0700 Subject: [PATCH 102/170] added a little bit of spin, too. --- examples/toys/grenade.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/toys/grenade.js b/examples/toys/grenade.js index 545bfbb982..b2dfebb888 100644 --- a/examples/toys/grenade.js +++ b/examples/toys/grenade.js @@ -166,13 +166,17 @@ function blowShitUp(position, radius) { var stuff = Entities.findEntities(position, radius); var numMoveable = 0; var STRENGTH = 3.5; + var SPIN_RATE = 20.0; for (var i = 0; i < stuff.length; i++) { var properties = Entities.getEntityProperties(stuff[i]); if (properties.collisionsWillMove) { var diff = Vec3.subtract(properties.position, position); var distance = Vec3.length(diff); var velocity = Vec3.sum(properties.velocity, Vec3.multiply(STRENGTH * 1.0 / distance, Vec3.normalize(diff))); - Entities.editEntity(stuff[i], { velocity: velocity }); + var angularVelocity = { x: Math.random() * SPIN_RATE, y: Math.random() * SPIN_RATE, z: Math.random() * SPIN_RATE }; + angularVelocity = Vec3.multiply( 1.0 / distance, angularVelocity); + Entities.editEntity(stuff[i], { velocity: velocity, + angularVelocity: angularVelocity }); } } } From cee570d2b26fb787e0f04dc26ed5ec692e949144 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Thu, 20 Aug 2015 23:06:05 -0700 Subject: [PATCH 103/170] First pass at basketball, just appears in front of you with some reasonable values --- examples/toys/basketball.js | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 examples/toys/basketball.js diff --git a/examples/toys/basketball.js b/examples/toys/basketball.js new file mode 100644 index 0000000000..d30dce6e72 --- /dev/null +++ b/examples/toys/basketball.js @@ -0,0 +1,82 @@ +// +// basketball.js +// examples +// +// Created by Philip Rosedale on August 20, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var basketballURL = HIFI_PUBLIC_BUCKET + "models/content/basketball2.fbx"; +var collisionSoundURL = HIFI_PUBLIC_BUCKET + "sounds/basketball/basketball.wav"; + + +var basketball = null; +var originalPosition = null; +var hasMoved = false; + +var GRAVITY = -9.8; +var DISTANCE_IN_FRONT_OF_ME = 1.0; +var START_MOVE = 0.01; +var DIAMETER = 0.30; + +function makeBasketball() { + var position = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, + { x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME })); + var rotation = Quat.multiply(MyAvatar.orientation, + Quat.fromPitchYawRollDegrees(0, -90, 0)); + basketball = Entities.addEntity({ + type: "Model", + position: position, + rotation: rotation, + dimensions: { x: DIAMETER, + y: DIAMETER, + z: DIAMETER }, + collisionsWillMove: true, + collisionSoundURL: collisionSoundURL, + modelURL: basketballURL, + restitution: 1.0, + linearDamping: 0.00001, + shapeType: "sphere" + }); + originalPosition = position; +} + +function update() { + if (!basketball) { + makeBasketball(); + } else { + var newProperties = Entities.getEntityProperties(basketball); + var moved = Vec3.length(Vec3.subtract(originalPosition, newProperties.position)); + if (!hasMoved && (moved > START_MOVE)) { + hasMoved = true; + Entities.editEntity(basketball, { gravity: {x: 0, y: GRAVITY, z: 0 }}); + } + var MAX_DISTANCE = 10.0; + var distance = Vec3.length(Vec3.subtract(MyAvatar.position, newProperties.position)); + if (distance > MAX_DISTANCE) { + deleteStuff(); + } + } +} + +function scriptEnding() { + deleteStuff(); +} + +function deleteStuff() { + if (basketball != null) { + Entities.deleteEntity(basketball); + basketball = null; + hasMoved = false; + } +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + From 76f236adf61f4103298cb3d37beeff688161efbb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 12 Aug 2015 22:44:16 -0700 Subject: [PATCH 104/170] New stereo rendering implementation --- interface/src/Application.cpp | 111 ++++++---------- interface/src/Stars.cpp | 4 +- interface/src/ui/ApplicationCompositor.cpp | 2 - interface/src/ui/ApplicationOverlay.cpp | 1 - .../src/display-plugins/DisplayPlugin.cpp | 2 +- .../src/display-plugins/DisplayPlugin.h | 4 + .../oculus/OculusBaseDisplayPlugin.cpp | 8 +- .../oculus/OculusBaseDisplayPlugin.h | 4 +- .../stereo/SideBySideStereoDisplayPlugin.cpp | 2 +- .../stereo/StereoDisplayPlugin.cpp | 12 +- .../stereo/StereoDisplayPlugin.h | 3 + libraries/gpu/src/gpu/Batch.cpp | 15 +++ libraries/gpu/src/gpu/Batch.h | 18 ++- libraries/gpu/src/gpu/Context.cpp | 35 +++++ libraries/gpu/src/gpu/Context.h | 44 ++++++- libraries/gpu/src/gpu/Forward.h | 77 +++++++++++ libraries/gpu/src/gpu/GLBackend.cpp | 80 ++++++------ libraries/gpu/src/gpu/GLBackend.h | 27 ++-- libraries/gpu/src/gpu/GLBackendOutput.cpp | 3 + libraries/gpu/src/gpu/GLBackendState.cpp | 6 + libraries/gpu/src/gpu/GLBackendTransform.cpp | 121 +++++++++++------- .../input-plugins/ViveControllerManager.cpp | 1 - .../src/AmbientOcclusionEffect.cpp | 1 - .../src/DeferredLightingEffect.cpp | 5 + .../render-utils/src/RenderDeferredTask.cpp | 65 ++++++---- libraries/render/src/render/DrawStatus.cpp | 5 +- libraries/render/src/render/DrawTask.cpp | 6 +- 27 files changed, 452 insertions(+), 210 deletions(-) create mode 100644 libraries/gpu/src/gpu/Forward.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b9bafbab1a..68b2322394 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1058,29 +1058,18 @@ void Application::paintGL() { // Using the latter will cause the camera to wobble with idle animations, // or with changes from the face tracker renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; - - if (!getActiveDisplayPlugin()->isHmd()) { - _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); - _myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation()); - } else { - mat4 camMat = _myAvatar->getSensorToWorldMatrix() * _myAvatar->getHMDSensorMatrix(); - _myCamera.setPosition(extractTranslation(camMat)); - _myCamera.setRotation(glm::quat_cast(camMat)); - } + _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); + _myCamera.setRotation(_myAvatar->getOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { - if (isHMDMode()) { - _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()); - } else { - _myCamera.setRotation(_myAvatar->getHead()->getOrientation()); - } - if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { - _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + - _myCamera.getRotation() * glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale()); - } else { - _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + - _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale()); - } + _myCamera.setRotation(_myAvatar->getOrientation()); + // https://www.youtube.com/watch?v=pFriRcIwqNU + vec3 boomStick = glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale(); + quat boomRotation = _myAvatar->getOrientation(); + if (!isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { + boomRotation = _myCamera.getRotation(); + } + _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + boomRotation * boomStick); } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + @@ -1089,7 +1078,6 @@ void Application::paintGL() { glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; } - // Update camera position if (!isHMDMode()) { _myCamera.update(1.0f / _fps); @@ -1105,57 +1093,37 @@ void Application::paintGL() { QSize size = DependencyManager::get()->getFrameBufferSize(); renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height()); - { - PROFILE_RANGE(__FUNCTION__ "/clear"); - doInBatch(&renderArgs, [&](gpu::Batch& batch) { - auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); - batch.setFramebuffer(primaryFbo); - // clear the normal and specular buffers - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | - gpu::Framebuffer::BUFFER_COLOR1 | - gpu::Framebuffer::BUFFER_COLOR2 | - gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(0), 1), 1.0, 0.0); - }); - } + doInBatch(&renderArgs, [&](gpu::Batch& batch) { + auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); + batch.setFramebuffer(primaryFbo); + // clear the normal and specular buffers + batch.clearFramebuffer( + gpu::Framebuffer::BUFFER_COLOR0 | + gpu::Framebuffer::BUFFER_COLOR1 | + gpu::Framebuffer::BUFFER_COLOR2 | + gpu::Framebuffer::BUFFER_DEPTH, + vec4(vec3(0), 1), 1.0, 0.0); + }); + renderArgs._viewport = gpu::Vec4i(0, 0, size.width(), size.height()); if (displayPlugin->isStereo()) { - PROFILE_RANGE(__FUNCTION__ "/stereoRender"); - QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height())); - glEnable(GL_SCISSOR_TEST); - for_each_eye([&](Eye eye){ - // Load the view frustum, used by meshes - Camera eyeCamera; - if (qApp->isHMDMode()) { - // Allow the displayPlugin to compose the final eye transform, based on the most up-to-date head motion. - eyeCamera.setTransform(displayPlugin->getModelview(eye, _myAvatar->getSensorToWorldMatrix())); - } else { - eyeCamera.setTransform(displayPlugin->getModelview(eye, _myCamera.getTransform())); - } - eyeCamera.setProjection(displayPlugin->getProjection(eye, _myCamera.getProjection())); - renderArgs._viewport = toGlm(currentViewport); - doInBatch(&renderArgs, [&](gpu::Batch& batch) { - batch.setViewportTransform(renderArgs._viewport); - batch.setStateScissorRect(renderArgs._viewport); - }); - displaySide(&renderArgs, eyeCamera); - }, [&] { - currentViewport.moveLeft(currentViewport.width()); + //_myCamera.setProjection(displayPlugin->getProjection(Mono, _myCamera.getProjection())); + renderArgs._context->enableStereo(true); + mat4 eyeViews[2]; + mat4 eyeProjections[2]; + auto baseProjection = renderArgs._viewFrustum->getProjection(); + // FIXME we don't need to set these every frame, + // only when the display plugin changes + for_each_eye([&](Eye eye) { + eyeViews[eye] = displayPlugin->getModelview(eye, mat4()); + eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection); }); - glDisable(GL_SCISSOR_TEST); - } else { - PROFILE_RANGE(__FUNCTION__ "/monoRender"); - renderArgs._viewport = gpu::Vec4i(0, 0, size.width(), size.height()); - // Viewport is assigned to the size of the framebuffer - doInBatch(&renderArgs, [&](gpu::Batch& batch) { - batch.setViewportTransform(renderArgs._viewport); - batch.setStateScissorRect(renderArgs._viewport); - }); - displaySide(&renderArgs, _myCamera); + renderArgs._context->setStereoProjections(eyeProjections); + renderArgs._context->setStereoViews(eyeViews); } - - doInBatch(&renderArgs, [](gpu::Batch& batch){ + displaySide(&renderArgs, _myCamera); + renderArgs._context->enableStereo(false); + doInBatch(&renderArgs, [](gpu::Batch& batch) { batch.setFramebuffer(nullptr); }); } @@ -4997,6 +4965,11 @@ mat4 Application::getHMDSensorPose() const { return mat4(); } +// FIXME there is a bug in the fullscreen setting, where leaving +// fullscreen does not restore the window frame, making it difficult +// or impossible to move or size the window. +// Additionally, setting fullscreen isn't hiding the menu on windows +// make it useless for stereoscopic modes. void Application::setFullscreen(const QScreen* target) { if (!_window->isFullScreen()) { _savedGeometry = _window->geometry(); diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 42b1a3f2e2..119b9ed1a2 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -191,7 +191,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { auto geometryCache = DependencyManager::get(); auto textureCache = DependencyManager::get(); - gpu::Batch batch; + gpu::Batch& batch = *renderArgs->_batch; batch.setViewTransform(Transform()); batch.setProjectionTransform(renderArgs->_viewFrustum->getProjection()); batch.setModelTransform(Transform().setRotation(glm::inverse(renderArgs->_viewFrustum->getOrientation()) * @@ -219,6 +219,4 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { batch.setInputBuffer(VERTICES_SLOT, posView); batch.setInputBuffer(COLOR_SLOT, colView); batch.draw(gpu::Primitive::POINTS, STARFIELD_NUM_STARS); - - renderArgs->_context->render(batch); } diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index abc1e49101..98634d7aed 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -211,7 +211,6 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { //Handle fading and deactivation/activation of UI gpu::Batch batch; - renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get(); geometryCache->useSimpleDrawPipeline(batch); @@ -279,7 +278,6 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int vec2 canvasSize = qApp->getCanvasSize(); _textureAspectRatio = aspect(canvasSize); - renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get(); gpu::Batch batch; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 6f18cac127..7254295c2f 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -92,7 +92,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope renderStatsAndLogs(renderArgs); // currently renders nothing - renderArgs->_context->syncCache(); renderArgs->_context->render(batch); renderArgs->_batch = nullptr; // so future users of renderArgs don't try to use our batch diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index 2316ff70c4..8bfe8c20da 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -28,7 +28,7 @@ DisplayPluginList getDisplayPlugins() { // Stereo modes // FIXME fix stereo display plugins - //new SideBySideStereoDisplayPlugin(), + new SideBySideStereoDisplayPlugin(), //new InterleavedStereoDisplayPlugin(), // HMDs diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index 45a5923a1f..da3bc98135 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -107,6 +107,10 @@ public: static const glm::mat4 pose; return pose; } + virtual glm::vec3 getEyeOffset(Eye eye) const { + static const glm::vec3 offset; return offset; + } + virtual glm::mat4 getHeadPose() const { static const glm::mat4 pose; return pose; } diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp index 7d0fb705df..0490e82106 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp @@ -14,6 +14,9 @@ using namespace Oculus; +OculusBaseDisplayPlugin::OculusBaseDisplayPlugin() : _ipd(OVR_DEFAULT_IPD) { +} + void OculusBaseDisplayPlugin::activate() { glm::uvec2 eyeSizes[2]; ovr_for_each_eye([&](ovrEyeType eye) { @@ -27,9 +30,12 @@ void OculusBaseDisplayPlugin::activate() { ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); - _eyeOffsets[eye] = erd.HmdToEyeViewOffset; + // We handle the eye offsets slightly differently, using an _ipd in the base class + // _eyeOffsets[eye] = erd.HmdToEyeViewOffset; + _eyeOffsets[eye] = { 0, 0, 0 }; eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); }); + _ipd = ovrHmd_GetFloat(_hmd, OVR_KEY_IPD, _ipd); _desiredFramebufferSize = uvec2( eyeSizes[0].x + eyeSizes[1].x, std::max(eyeSizes[0].y, eyeSizes[1].y)); diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h index e376ae12ba..401ee6579a 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h @@ -11,6 +11,7 @@ class OculusBaseDisplayPlugin : public MainWindowOpenGLDisplayPlugin { public: + OculusBaseDisplayPlugin(); // Stereo specific methods virtual bool isHmd() const override { return true; } virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; @@ -22,5 +23,6 @@ public: virtual void resetSensors() override; virtual glm::mat4 getEyePose(Eye eye) const override; virtual glm::mat4 getHeadPose() const override; - +protected: + float _ipd; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp index e348143250..72ef5249a8 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -17,7 +17,7 @@ #include -const QString SideBySideStereoDisplayPlugin::NAME("SBS Stereo Display"); +const QString SideBySideStereoDisplayPlugin::NAME("Debug Stereo Display"); const QString & SideBySideStereoDisplayPlugin::getName() const { return NAME; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index c741967328..3d5f73bfa8 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -52,6 +52,16 @@ glm::mat4 StereoDisplayPlugin::getModelview(Eye eye, const glm::mat4& baseModelv void StereoDisplayPlugin::activate() { WindowOpenGLDisplayPlugin::activate(); - CONTAINER->setFullscreen(qApp->primaryScreen()); + // FIXME there is a bug in the fullscreen setting, see + // Application::setFullscreen + //CONTAINER->setFullscreen(qApp->primaryScreen()); // FIXME Add menu items } + +glm::vec3 StereoDisplayPlugin::getEyeOffset(Eye eye) const { + glm::vec3 result(_ipd / 2.0f, 0, 0); + if (eye == Eye::Right) { + result *= -1.0f; + } + return result; +} diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index bb1a4fd42c..77b1141aed 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -20,5 +20,8 @@ public: virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; + virtual glm::vec3 getEyeOffset(Eye eye) const override; +protected: + float _ipd{ 0.064f }; }; diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 3ecbc3b2f3..fb6618e953 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -288,3 +288,18 @@ void Batch::resetStages() { ADD_COMMAND(resetStages); } +void Batch::enableStereo(bool enable) { + _enableStereo = enable; +} + +bool Batch::isStereoEnabled() const { + return _enableStereo; +} + +void Batch::enableSkybox(bool enable) { + _enableSkybox = enable; +} + +bool Batch::isSkyboxEnabled() const { + return _enableSkybox; +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index d48ffef209..ca74032c5e 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -26,7 +26,7 @@ ProfileRange(const char *name); ~ProfileRange(); }; - #define PROFILE_RANGE(name) ProfileRange profileRangeThis(name); +#define PROFILE_RANGE(name) ProfileRange profileRangeThis(name); #else #define PROFILE_RANGE(name) #endif @@ -47,6 +47,19 @@ public: ~Batch(); void clear(); + + // Batches may need to override the context level stereo settings + // if they're performing framebuffer copy operations, like the + // deferred lighting resolution mechanism + void enableStereo(bool enable = true); + bool isStereoEnabled() const; + + // Stereo batches will pre-translate the view matrix, but this isn't + // appropriate for skyboxes or other things intended to be drawn at + // infinite distance, so provide a mechanism to render in stereo + // without the pre-translation of the view. + void enableSkybox(bool enable = true); + bool isSkyboxEnabled() const; // Drawcalls void draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex = 0); @@ -276,6 +289,9 @@ public: FramebufferCaches _framebuffers; QueryCaches _queries; + bool _enableStereo{ true }; + bool _enableSkybox{ false }; + protected: }; diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 239c460c77..561a51b477 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -40,6 +40,18 @@ void Context::render(Batch& batch) { _backend->render(batch); } +void Context::enableStereo(bool enable) { + _backend->enableStereo(enable); +} + +void Context::setStereoProjections(const mat4 eyeProjections[2]) { + _backend->setStereoProjections(eyeProjections); +} + +void Context::setStereoViews(const mat4 eyeViews[2]) { + _backend->setStereoViews(eyeViews); +} + void Context::syncCache() { PROFILE_RANGE(__FUNCTION__); _backend->syncCache(); @@ -49,3 +61,26 @@ void Context::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, cons _backend->downloadFramebuffer(srcFramebuffer, region, destImage); } +const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived() const { + _projectionInverse = glm::inverse(_projection); + _viewInverse = glm::inverse(_view); + + Mat4 viewUntranslated = _view; + viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); + _projectionViewUntranslated = _projection * viewUntranslated; + return *this; +} + +Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const StereoState& _stereo) const { + TransformCamera result = *this; + if (!_stereo._skybox) { + result._view = _stereo._eyeViews[eye] * result._view; + } else { + glm::mat4 skyboxView = _stereo._eyeViews[eye]; + skyboxView[3] = vec4(0, 0, 0, 1); + result._view = skyboxView * result._view; + } + result._projection = _stereo._eyeProjections[eye]; + result.recomputeDerived(); + return result; +} diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 7158bd1a6d..9aca83e577 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -14,6 +14,8 @@ #include #include +#include + #include "Batch.h" #include "Resource.h" @@ -25,28 +27,58 @@ class QImage; namespace gpu { +struct StereoState { + bool _enable{ false }; + bool _skybox{ false }; + // 0 for left eye, 1 for right eye + uint8_t _pass{ 0 }; + mat4 _eyeViews[2]; + mat4 _eyeProjections[2]; +}; + class Backend { public: virtual~ Backend() {}; virtual void render(Batch& batch) = 0; + virtual void enableStereo(bool enable) { + _stereo._enable = enable; + } + + void setStereoProjections(const mat4 eyeProjections[2]) { + for (int i = 0; i < 2; ++i) { + _stereo._eyeProjections[i] = eyeProjections[i]; + } + } + + void setStereoViews(const mat4 views[2]) { + for (int i = 0; i < 2; ++i) { + _stereo._eyeViews[i] = views[i]; + } + } + virtual void syncCache() = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; + // UBO class... layout MUST match the layout in TransformCamera.slh class TransformObject { public: Mat4 _model; Mat4 _modelInverse; }; + // UBO class... layout MUST match the layout in TransformCamera.slh class TransformCamera { public: Mat4 _view; - Mat4 _viewInverse; - Mat4 _projectionViewUntranslated; + mutable Mat4 _viewInverse; + mutable Mat4 _projectionViewUntranslated; Mat4 _projection; - Mat4 _projectionInverse; + mutable Mat4 _projectionInverse; Vec4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations. + + const Backend::TransformCamera& recomputeDerived() const; + TransformCamera getEyeCamera(int eye, const StereoState& stereo) const; }; template< typename T > @@ -113,7 +145,7 @@ public: } protected: - + StereoState _stereo; }; class Context { @@ -136,7 +168,9 @@ public: ~Context(); void render(Batch& batch); - + void enableStereo(bool enable = true); + void setStereoProjections(const mat4 eyeProjections[2]); + void setStereoViews(const mat4 eyeViews[2]); void syncCache(); // Downloading the Framebuffer is a synchronous action that is not efficient. diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h new file mode 100644 index 0000000000..0fa315ef1c --- /dev/null +++ b/libraries/gpu/src/gpu/Forward.h @@ -0,0 +1,77 @@ +// +// Created by Bradley Austin Davis on 2015/08/15 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_gpu_Forward_h +#define hifi_gpu_Forward_h + +namespace gpu { + class Batch; + class Backend; + class Context; + typedef std::shared_ptr ContextPointer; + class GPUObject; + + typedef int Stamp; + typedef uint32_t uint32; + typedef int32_t int32; + typedef uint16_t uint16; + typedef int16_t int16; + typedef uint8_t uint8; + typedef int8_t int8; + + typedef uint8 Byte; + typedef uint32 Offset; + typedef std::vector Offsets; + + typedef glm::mat4 Mat4; + typedef glm::mat3 Mat3; + typedef glm::vec4 Vec4; + typedef glm::ivec4 Vec4i; + typedef glm::vec3 Vec3; + typedef glm::vec2 Vec2; + typedef glm::ivec2 Vec2i; + typedef glm::uvec2 Vec2u; + + class Element; + typedef Element Format; + class Swapchain; + typedef std::shared_ptr SwapchainPointer; + class Framebuffer; + typedef std::shared_ptr FramebufferPointer; + class Pipeline; + typedef std::shared_ptr PipelinePointer; + typedef std::vector Pipelines; + class Query; + typedef std::shared_ptr QueryPointer; + typedef std::vector Queries; + class Resource; + class Buffer; + typedef std::shared_ptr BufferPointer; + typedef std::vector Buffers; + class BufferView; + class Shader; + typedef Shader::Pointer ShaderPointer; + typedef std::vector Shaders; + class State; + typedef std::shared_ptr StatePointer; + typedef std::vector States; + class Stream; + class BufferStream; + typedef std::shared_ptr BufferStreamPointer; + class Texture; + class SphericalHarmonics; + typedef std::shared_ptr SHPointer; + class Sampler; + class Texture; + typedef std::shared_ptr TexturePointer; + typedef std::vector Textures; + class TextureView; + typedef std::vector TextureViews; +} + +#endif diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 4afd2ba940..2270c0dce7 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -127,39 +127,29 @@ void GLBackend::renderPassTransfer(Batch& batch) { const Batch::Commands::value_type* command = batch.getCommands().data(); const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); - _transform._cameraTransforms.resize(0); - _transform._cameraTransforms.push_back(TransformCamera()); + // Reset the transform buffers + _transform._cameras.resize(0); _transform._cameraOffsets.clear(); - _transform._cameraOffsets.push_back(TransformStageState::Pair(0, 0)); - - _transform._objectTransforms.push_back(TransformObject()); - _transform._objectOffsets.push_back(TransformStageState::Pair(0, 0)); + _transform._objects.resize(0); _transform._objectOffsets.clear(); - _transform._objectTransforms.resize(0); - - _commandIndex = 0; - preUpdateTransform(); - int drawCount = 0; for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { switch (*command) { case Batch::COMMAND_draw: case Batch::COMMAND_drawIndexed: case Batch::COMMAND_drawInstanced: case Batch::COMMAND_drawIndexedInstanced: - preUpdateTransform(); - ++drawCount; + _transform.preUpdate(_commandIndex, _stereo); break; case Batch::COMMAND_setModelTransform: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: - case Batch::COMMAND_setProjectionTransform: - { + case Batch::COMMAND_setProjectionTransform: { CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); + break; } - break; default: break; @@ -167,44 +157,31 @@ void GLBackend::renderPassTransfer(Batch& batch) { command++; offset++; } - - - static QByteArray bufferData; - glBindBuffer(GL_UNIFORM_BUFFER, _transform._transformCameraBuffer); - bufferData.resize(_transform._cameraUboSize * _transform._cameraTransforms.size()); - for (size_t i = 0; i < _transform._cameraTransforms.size(); ++i) { - memcpy(bufferData.data() + (_transform._cameraUboSize * i), &_transform._cameraTransforms[i], sizeof(TransformCamera)); - } - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, _transform._transformObjectBuffer); - bufferData.resize(_transform._objectUboSize * _transform._objectTransforms.size()); - for (size_t i = 0; i < _transform._objectTransforms.size(); ++i) { - memcpy(bufferData.data() + (_transform._objectUboSize * i), &_transform._objectTransforms[i], sizeof(TransformObject)); - } - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - CHECK_GL_ERROR(); + _transform.transfer(); } void GLBackend::renderPassDraw(Batch& batch) { + _transform._objectsItr = _transform._objectOffsets.begin(); + _transform._camerasItr = _transform._cameraOffsets.begin(); const size_t numCommands = batch.getCommands().size(); const Batch::Commands::value_type* command = batch.getCommands().data(); const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { switch (*command) { // Ignore these commands on this pass, taken care of in the transfer pass + // Note we allow COMMAND_setViewportTransform to occur in both passes + // as it both updates the transform object (and thus the uniforms in the + // UBO) as well as executes the actual viewport call case Batch::COMMAND_setModelTransform: - case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: break; - default: - { + default: { CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); + break; } - break; } command++; @@ -213,8 +190,33 @@ void GLBackend::renderPassDraw(Batch& batch) { } void GLBackend::render(Batch& batch) { - renderPassTransfer(batch); - renderPassDraw(batch); + _stereo._skybox = batch.isSkyboxEnabled(); + // Allow the batch to override the rendering stereo settings + // for things like full framebuffer copy operations (deferred lighting passes) + bool savedStereo = _stereo._enable; + if (!batch.isStereoEnabled()) { + _stereo._enable = false; + } + + { + PROFILE_RANGE("Transfer"); + renderPassTransfer(batch); + } + + { + PROFILE_RANGE(_stereo._enable ? "LeftRender" : "Render"); + renderPassDraw(batch); + } + + if (_stereo._enable) { + PROFILE_RANGE("RightRender"); + _stereo._pass = 1; + renderPassDraw(batch); + _stereo._pass = 0; + } + + // Restore the saved stereo state for the next batch + _stereo._enable = savedStereo; } bool GLBackend::checkGLError(const char* name) { diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 455240deb8..c97ea4e615 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -310,19 +310,22 @@ protected: void killTransform(); // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncTransformStateCache(); - void updateTransform(); - void preUpdateTransform(); + void updateTransform() const; void resetTransformStage(); - struct TransformStageState { - TransformObject _transformObject; - TransformCamera _transformCamera; - std::vector _objectTransforms; - std::vector _cameraTransforms; + struct TransformStageState { + using TransformObjects = std::vector; + using TransformCameras = std::vector; + + TransformObject _object; + TransformCamera _camera; + TransformObjects _objects; + TransformCameras _cameras; + size_t _cameraUboSize{ 0 }; size_t _objectUboSize{ 0 }; - GLuint _transformObjectBuffer{ 0 }; - GLuint _transformCameraBuffer{ 0 }; + GLuint _objectBuffer{ 0 }; + GLuint _cameraBuffer{ 0 }; Transform _model; Transform _view; Mat4 _projection; @@ -336,6 +339,12 @@ protected: using List = std::list; List _cameraOffsets; List _objectOffsets; + mutable List::const_iterator _objectsItr; + mutable List::const_iterator _camerasItr; + + void preUpdate(size_t commandIndex, const StereoState& stereo); + void update(size_t commandIndex, const StereoState& stereo) const; + void transfer() const; } _transform; int32_t _uboAlignment{ 0 }; diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp index d75d0cf521..33ae1dd0a3 100755 --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -198,6 +198,9 @@ void GLBackend::do_setFramebuffer(Batch& batch, uint32 paramOffset) { } void GLBackend::do_clearFramebuffer(Batch& batch, uint32 paramOffset) { + if (_stereo._enable && !_pipeline._stateCache.scissorEnable) { + qWarning("Clear without scissor in stereo mode"); + } uint32 masks = batch._params[paramOffset + 7]._uint; Vec4 color; diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu/src/gpu/GLBackendState.cpp index c5cc987fd1..9fdcbc0870 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu/src/gpu/GLBackendState.cpp @@ -768,6 +768,12 @@ void GLBackend::do_setStateScissorRect(Batch& batch, uint32 paramOffset) { Vec4i rect; memcpy(&rect, batch.editData(batch._params[paramOffset]._uint), sizeof(Vec4i)); + if (_stereo._enable) { + rect.z /= 2; + if (_stereo._pass) { + rect.x += rect.z; + } + } glScissor(rect.x, rect.y, rect.z, rect.w); (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index 23b3dba14e..e33a8f8cde 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -33,16 +33,26 @@ void GLBackend::do_setProjectionTransform(Batch& batch, uint32 paramOffset) { void GLBackend::do_setViewportTransform(Batch& batch, uint32 paramOffset) { memcpy(&_transform._viewport, batch.editData(batch._params[paramOffset]._uint), sizeof(Vec4i)); + ivec4& vp = _transform._viewport; + // Where we assign the GL viewport - glViewport(_transform._viewport.x, _transform._viewport.y, _transform._viewport.z, _transform._viewport.w); + if (_stereo._enable) { + vp.z /= 2; + if (_stereo._pass) { + vp.x += vp.z; + } + int i = 0; + } + + glViewport(vp.x, vp.y, vp.z, vp.w); // The Viewport is tagged invalid because the CameraTransformUBO is not up to date and willl need update on next drawcall _transform._invalidViewport = true; } void GLBackend::initTransform() { - glGenBuffers(1, &_transform._transformObjectBuffer); - glGenBuffers(1, &_transform._transformCameraBuffer); + glGenBuffers(1, &_transform._objectBuffer); + glGenBuffers(1, &_transform._cameraBuffer); size_t cameraSize = sizeof(TransformCamera); while (_transform._cameraUboSize < cameraSize) { _transform._cameraUboSize += _uboAlignment; @@ -54,8 +64,8 @@ void GLBackend::initTransform() { } void GLBackend::killTransform() { - glDeleteBuffers(1, &_transform._transformObjectBuffer); - glDeleteBuffers(1, &_transform._transformCameraBuffer); + glDeleteBuffers(1, &_transform._objectBuffer); + glDeleteBuffers(1, &_transform._cameraBuffer); } void GLBackend::syncTransformStateCache() { @@ -72,73 +82,98 @@ void GLBackend::syncTransformStateCache() { _transform._model.setIdentity(); } -void GLBackend::preUpdateTransform() { +void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo) { // Check all the dirty flags and update the state accordingly - if (_transform._invalidViewport) { - _transform._transformCamera._viewport = glm::vec4(_transform._viewport); + if (_invalidViewport) { + _camera._viewport = glm::vec4(_viewport); } - if (_transform._invalidProj) { - _transform._transformCamera._projection = _transform._projection; - _transform._transformCamera._projectionInverse = glm::inverse(_transform._projection); + if (_invalidProj) { + _camera._projection = _projection; } - if (_transform._invalidView) { - _transform._view.getInverseMatrix(_transform._transformCamera._view); - _transform._view.getMatrix(_transform._transformCamera._viewInverse); + if (_invalidView) { + _view.getInverseMatrix(_camera._view); } - if (_transform._invalidModel) { - _transform._model.getMatrix(_transform._transformObject._model); - _transform._model.getInverseMatrix(_transform._transformObject._modelInverse); + if (_invalidModel) { + _model.getMatrix(_object._model); + _model.getInverseMatrix(_object._modelInverse); } - if (_transform._invalidView || _transform._invalidProj) { - Mat4 viewUntranslated = _transform._transformCamera._view; - viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); - _transform._transformCamera._projectionViewUntranslated = _transform._transformCamera._projection * viewUntranslated; + if (_invalidView || _invalidProj || _invalidViewport) { + size_t offset = _cameraUboSize * _cameras.size(); + if (stereo._enable) { + _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); + for (int i = 0; i < 2; ++i) { + _cameras.push_back(_camera.getEyeCamera(i, stereo)); + } + } else { + _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); + _cameras.push_back(_camera.recomputeDerived()); + } } - if (_transform._invalidView || _transform._invalidProj || _transform._invalidViewport) { - _transform._cameraOffsets.push_back(TransformStageState::Pair(_commandIndex, _transform._cameraUboSize * _transform._cameraTransforms.size())); - _transform._cameraTransforms.push_back(_transform._transformCamera); - } - - if (_transform._invalidModel) { - _transform._objectOffsets.push_back(TransformStageState::Pair(_commandIndex, _transform._objectUboSize * _transform._objectTransforms.size())); - _transform._objectTransforms.push_back(_transform._transformObject); + if (_invalidModel) { + size_t offset = _objectUboSize * _objects.size(); + _objectOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); + _objects.push_back(_object); } // Flags are clean - _transform._invalidView = _transform._invalidProj = _transform._invalidModel = _transform._invalidViewport = false; + _invalidView = _invalidProj = _invalidModel = _invalidViewport = false; } -void GLBackend::updateTransform() { +void GLBackend::TransformStageState::transfer() const { + static QByteArray bufferData; + glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer); + bufferData.resize(_cameraUboSize * _cameras.size()); + for (size_t i = 0; i < _cameras.size(); ++i) { + memcpy(bufferData.data() + (_cameraUboSize * i), &_cameras[i], sizeof(TransformCamera)); + } + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, _objectBuffer); + bufferData.resize(_objectUboSize * _objects.size()); + for (size_t i = 0; i < _objects.size(); ++i) { + memcpy(bufferData.data() + (_objectUboSize * i), &_objects[i], sizeof(TransformObject)); + } + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + CHECK_GL_ERROR(); +} + +void GLBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo) const { int offset = -1; - while (!_transform._objectOffsets.empty() && _commandIndex >= _transform._objectOffsets.front().first) { - offset = _transform._objectOffsets.front().second; - _transform._objectOffsets.pop_front(); + while ((_objectsItr != _objectOffsets.end()) && (commandIndex >= (*_objectsItr).first)) { + offset = (*_objectsItr).second; + ++_objectsItr; } if (offset >= 0) { - glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT, - _transform._transformObjectBuffer, - offset, sizeof(Backend::TransformObject)); + glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT, + _objectBuffer, offset, sizeof(Backend::TransformObject)); } offset = -1; - while (!_transform._cameraOffsets.empty() && _commandIndex >= _transform._cameraOffsets.front().first) { - offset = _transform._cameraOffsets.front().second; - _transform._cameraOffsets.pop_front(); + while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { + offset = (*_camerasItr).second; + ++_camerasItr; } if (offset >= 0) { - glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, - _transform._transformCameraBuffer, - offset, sizeof(Backend::TransformCamera)); + // We include both camera offsets for stereo + if (stereo._enable && stereo._pass) { + offset += _cameraUboSize; + } + glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, + _cameraBuffer, offset, sizeof(Backend::TransformCamera)); } (void)CHECK_GL_ERROR(); } +void GLBackend::updateTransform() const { + _transform.update(_commandIndex, _stereo); +} + void GLBackend::resetTransformStage() { } diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp index fc53670fd0..e76983cce9 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -189,7 +189,6 @@ void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePoint renderHand(rightHand, batch, RIGHT_HAND); } - args->_context->syncCache(); args->_context->render(batch); } } diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 5f0afd37d1..ecf7c34dfe 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -246,6 +246,5 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); // Ready to render - args->_context->syncCache(); args->_context->render((batch)); } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 6776fa6c79..a785a5d2ec 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -225,6 +225,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu void DeferredLightingEffect::prepare(RenderArgs* args) { gpu::Batch batch; + batch.enableStereo(false); batch.setStateScissorRect(args->_viewport); @@ -244,6 +245,9 @@ gpu::FramebufferPointer _copyFBO; void DeferredLightingEffect::render(RenderArgs* args) { gpu::Batch batch; + // Framebuffer copy operations cannot function as multipass stereo operations. + batch.enableStereo(false); + // perform deferred lighting, rendering to free fbo auto framebufferCache = DependencyManager::get(); @@ -555,6 +559,7 @@ void DeferredLightingEffect::render(RenderArgs* args) { void DeferredLightingEffect::copyBack(RenderArgs* args) { gpu::Batch batch; + batch.enableStereo(false); auto framebufferCache = DependencyManager::get(); QSize framebufferSize = framebufferCache->getFrameBufferSize(); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index efef423623..ca3f87f53f 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -35,6 +35,7 @@ void SetupDeferred::run(const SceneContextPointer& sceneContext, const RenderCon auto primaryFbo = DependencyManager::get()->getPrimaryFramebufferDepthColor(); gpu::Batch batch; + batch.enableStereo(false); batch.setFramebuffer(nullptr); batch.setFramebuffer(primaryFbo); @@ -159,6 +160,8 @@ void DrawOpaqueDeferred::run(const SceneContextPointer& sceneContext, const Rend RenderArgs* args = renderContext->args; gpu::Batch batch; + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); args->_batch = &batch; renderContext->_numDrawnOpaqueItems = inItems.size(); @@ -188,6 +191,8 @@ void DrawTransparentDeferred::run(const SceneContextPointer& sceneContext, const RenderArgs* args = renderContext->args; gpu::Batch batch; + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); args->_batch = &batch; renderContext->_numDrawnTransparentItems = inItems.size(); @@ -247,30 +252,42 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon renderContext->_numFeedOverlay3DItems = inItems.size(); renderContext->_numDrawnOverlay3DItems = inItems.size(); - RenderArgs* args = renderContext->args; - gpu::Batch batch; - args->_batch = &batch; - args->_whiteTexture = DependencyManager::get()->getWhiteTexture(); - - glm::mat4 projMat; - Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - batch.setPipeline(getOpaquePipeline()); - batch.setResourceTexture(0, args->_whiteTexture); - if (!inItems.empty()) { - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, true); - renderItems(sceneContext, renderContext, inItems, renderContext->_maxDrawnOverlay3DItems); - } + RenderArgs* args = renderContext->args; - args->_context->render((*args->_batch)); - args->_batch = nullptr; - args->_whiteTexture.reset(); + // Clear the framebuffer without stereo + // Needs to be distinct from the other batch because using the clear call + // while stereo is enabled triggers a warning + { + gpu::Batch batch; + batch.enableStereo(false); + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, true); + args->_context->render(batch); + } + + // Render the items + { + gpu::Batch batch; + args->_batch = &batch; + args->_whiteTexture = DependencyManager::get()->getWhiteTexture(); + + glm::mat4 projMat; + Transform viewMat; + args->_viewFrustum->evalProjectionMatrix(projMat); + args->_viewFrustum->evalViewTransform(viewMat); + + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + batch.setPipeline(getOpaquePipeline()); + batch.setResourceTexture(0, args->_whiteTexture); + renderItems(sceneContext, renderContext, inItems, renderContext->_maxDrawnOverlay3DItems); + + args->_context->render((*args->_batch)); + args->_batch = nullptr; + args->_whiteTexture.reset(); + } + } } diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 0c1b92b559..28b4344bf1 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -161,8 +161,5 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContex batch.draw(gpu::TRIANGLES, 24, 0); } - // Before rendering the batch make sure we re in sync with gl state - args->_context->syncCache(); - renderContext->args->_context->syncCache(); - args->_context->render((batch)); + args->_context->render(batch); } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index ff2cf6ff41..0754d81bde 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -258,6 +258,9 @@ void DrawBackground::run(const SceneContextPointer& sceneContext, const RenderCo } RenderArgs* args = renderContext->args; gpu::Batch batch; + batch.enableSkybox(true); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); args->_batch = &batch; glm::mat4 projMat; @@ -271,9 +274,6 @@ void DrawBackground::run(const SceneContextPointer& sceneContext, const RenderCo renderItems(sceneContext, renderContext, inItems); args->_context->render((*args->_batch)); args->_batch = nullptr; - - // Force the context sync - args->_context->syncCache(); } void ItemMaterialBucketMap::insert(const ItemID& id, const model::MaterialKey& key) { From 7fb491e48c2507db85391a2ed3d7cf282fa31b7f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 19 Aug 2015 19:09:21 -0700 Subject: [PATCH 105/170] Cleanup plugin interface, break up oculus plugins --- cmake/externals/LibOVR/CMakeLists.txt | 15 ++ interface/src/Application.cpp | 37 ++- .../Basic2DWindowOpenGLDisplayPlugin.cpp | 4 +- .../Basic2DWindowOpenGLDisplayPlugin.h | 4 +- .../src/display-plugins/DisplayPlugin.cpp | 12 +- .../src/display-plugins/DisplayPlugin.h | 15 +- .../MainWindowOpenGLDisplayPlugin.cpp | 9 - .../MainWindowOpenGLDisplayPlugin.h | 13 - .../oculus/OculusBaseDisplayPlugin.cpp | 82 ------ .../oculus/OculusBaseDisplayPlugin.h | 28 --- ...playPlugin.cpp => OculusDisplayPlugin.cpp} | 238 +++++++++++++----- .../oculus/OculusDisplayPlugin.h | 78 ++++++ .../display-plugins/oculus/OculusHelpers.h | 13 +- ...ugin.cpp => OculusLegacyDisplayPlugin.cpp} | 121 ++++++--- .../oculus/OculusLegacyDisplayPlugin.h | 63 +++++ .../oculus/Oculus_0_5_DisplayPlugin.h | 37 --- .../oculus/Oculus_0_6_DisplayPlugin.h | 41 --- .../openvr/OpenVrDisplayPlugin.cpp | 12 +- .../openvr/OpenVrDisplayPlugin.h | 5 +- .../stereo/StereoDisplayPlugin.cpp | 12 +- .../stereo/StereoDisplayPlugin.h | 7 +- 21 files changed, 465 insertions(+), 381 deletions(-) delete mode 100644 libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp delete mode 100644 libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h delete mode 100644 libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp delete mode 100644 libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h rename libraries/display-plugins/src/display-plugins/oculus/{Oculus_0_6_DisplayPlugin.cpp => OculusDisplayPlugin.cpp} (59%) create mode 100644 libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h rename libraries/display-plugins/src/display-plugins/oculus/{Oculus_0_5_DisplayPlugin.cpp => OculusLegacyDisplayPlugin.cpp} (55%) create mode 100644 libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h delete mode 100644 libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h delete mode 100644 libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index b0bf34a594..e03a3af484 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -5,6 +5,21 @@ set(EXTERNAL_NAME LibOVR) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +# These are all provided in order to allow easier testing of both +# the legacy display plugin and the new windows only plugin on +# various versions of the SDK, all on windows +# +# 0.5 public +# URL http://static.oculus.com/sdk-downloads/ovr_sdk_win_0.5.0.1.zip +# URL_MD5 d3fc4c02db9be5ff08af4ef4c97b32f9 +# 0.6 public +# URL http://static.oculus.com/sdk-downloads/0.6.0.1/Public/1435190862/ovr_sdk_win_0.6.0.1.zip +# URL_MD5 4b3ef825f9a1d6d3035c9f6820687da9 +# 0.7 alpha +# URL https://s3.amazonaws.com/static.oculus.com/sdk-downloads/0.7.0.0/Public/Alpha/ovr_sdk_win_0.7.0.0_RC1.zip +# URL_MD5 a562bb9d117087b2cf9d86653ea70fd8 + + if (WIN32) ExternalProject_Add( diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 68b2322394..374963e9b6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1058,18 +1058,29 @@ void Application::paintGL() { // Using the latter will cause the camera to wobble with idle animations, // or with changes from the face tracker renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; - _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); - _myCamera.setRotation(_myAvatar->getOrientation()); - } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { - _myCamera.setRotation(_myAvatar->getOrientation()); - // https://www.youtube.com/watch?v=pFriRcIwqNU - vec3 boomStick = glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale(); - quat boomRotation = _myAvatar->getOrientation(); - if (!isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { - boomRotation = _myCamera.getRotation(); - } - _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + boomRotation * boomStick); + if (!getActiveDisplayPlugin()->isHmd()) { + _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); + _myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation()); + } else { + mat4 camMat = _myAvatar->getSensorToWorldMatrix() * _myAvatar->getHMDSensorMatrix(); + _myCamera.setPosition(extractTranslation(camMat)); + _myCamera.setRotation(glm::quat_cast(camMat)); + } + } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { + if (isHMDMode()) { + _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()); + } else { + _myCamera.setRotation(_myAvatar->getHead()->getOrientation()); + } + if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { + _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + + _myCamera.getRotation() * glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale()); + } else { + _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + + _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale()); + } + } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + @@ -1115,7 +1126,7 @@ void Application::paintGL() { // FIXME we don't need to set these every frame, // only when the display plugin changes for_each_eye([&](Eye eye) { - eyeViews[eye] = displayPlugin->getModelview(eye, mat4()); + eyeViews[eye] = displayPlugin->getView(eye, mat4()); eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection); }); renderArgs._context->setStereoProjections(eyeProjections); @@ -4952,7 +4963,7 @@ mat4 Application::getEyePose(int eye) const { mat4 Application::getEyeOffset(int eye) const { if (isHMDMode()) { mat4 identity; - return getActiveDisplayPlugin()->getModelview((Eye)eye, identity); + return getActiveDisplayPlugin()->getView((Eye)eye, identity); } return mat4(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 7667fa1a29..14ba868275 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -30,11 +30,11 @@ void Basic2DWindowOpenGLDisplayPlugin::activate() { CONTAINER->unsetFullscreen(); } }, true, false); - MainWindowOpenGLDisplayPlugin::activate(); + WindowOpenGLDisplayPlugin::activate(); } void Basic2DWindowOpenGLDisplayPlugin::deactivate() { - MainWindowOpenGLDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); } int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval(bool isThrottled) const { diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 477e214f4e..64edfe3600 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -7,10 +7,10 @@ // #pragma once -#include "MainWindowOpenGLDisplayPlugin.h" +#include "WindowOpenGLDisplayPlugin.h" class QScreen; -class Basic2DWindowOpenGLDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +class Basic2DWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { Q_OBJECT public: diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index 8bfe8c20da..cd620d85a4 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -15,8 +15,8 @@ #include "Basic2DWindowOpenGLDisplayPlugin.h" #include "openvr/OpenVrDisplayPlugin.h" -#include "oculus/Oculus_0_5_DisplayPlugin.h" -#include "oculus/Oculus_0_6_DisplayPlugin.h" +#include "oculus/OculusDisplayPlugin.h" +#include "oculus/OculusLegacyDisplayPlugin.h" // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class DisplayPluginList getDisplayPlugins() { @@ -32,9 +32,13 @@ DisplayPluginList getDisplayPlugins() { //new InterleavedStereoDisplayPlugin(), // HMDs - new Oculus_0_5_DisplayPlugin(), - new Oculus_0_6_DisplayPlugin(), + + // Windows Oculus SDK + new OculusDisplayPlugin(), + // Mac/Linux Oculus SDK (0.5) + new OculusLegacyDisplayPlugin(), #ifdef Q_OS_WIN + // SteamVR SDK new OpenVrDisplayPlugin(), #endif nullptr diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index da3bc98135..1a4166c0fa 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -97,20 +97,16 @@ public: return baseProjection; } - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const { - return glm::inverse(getEyePose(eye)) * baseModelview; + virtual glm::mat4 getView(Eye eye, const glm::mat4& baseView) const { + return glm::inverse(getEyePose(eye)) * baseView; } // HMD specific methods - // TODO move these into another class + // TODO move these into another class? virtual glm::mat4 getEyePose(Eye eye) const { static const glm::mat4 pose; return pose; } - virtual glm::vec3 getEyeOffset(Eye eye) const { - static const glm::vec3 offset; return offset; - } - virtual glm::mat4 getHeadPose() const { static const glm::mat4 pose; return pose; } @@ -119,11 +115,6 @@ public: virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } - //// The window for the surface, used for event interception. May be null. - //virtual QWindow* getWindow() const = 0; - - //virtual void installEventFilter(QObject* filter) {} - //virtual void removeEventFilter(QObject* filter) {} signals: void recommendedFramebufferSizeChanged(const QSize & size); diff --git a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp deleted file mode 100644 index 68fe92c943..0000000000 --- a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp +++ /dev/null @@ -1,9 +0,0 @@ - -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "MainWindowOpenGLDisplayPlugin.h" diff --git a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h deleted file mode 100644 index 5b28ec7c21..0000000000 --- a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#pragma once - -#include "WindowOpenGLDisplayPlugin.h" - -class MainWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { -}; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp deleted file mode 100644 index 0490e82106..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "OculusBaseDisplayPlugin.h" - -#include - -#include "OculusHelpers.h" - - -using namespace Oculus; - -OculusBaseDisplayPlugin::OculusBaseDisplayPlugin() : _ipd(OVR_DEFAULT_IPD) { -} - -void OculusBaseDisplayPlugin::activate() { - glm::uvec2 eyeSizes[2]; - ovr_for_each_eye([&](ovrEyeType eye) { - _eyeFovs[eye] = _hmd->MaxEyeFov[eye]; - ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovrHmd_GetRenderDesc(_hmd, eye, _eyeFovs[eye]); - ovrMatrix4f ovrPerspectiveProjection = - ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); - _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); - - ovrPerspectiveProjection = - ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); - _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); - - // We handle the eye offsets slightly differently, using an _ipd in the base class - // _eyeOffsets[eye] = erd.HmdToEyeViewOffset; - _eyeOffsets[eye] = { 0, 0, 0 }; - eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); - }); - _ipd = ovrHmd_GetFloat(_hmd, OVR_KEY_IPD, _ipd); - _desiredFramebufferSize = uvec2( - eyeSizes[0].x + eyeSizes[1].x, - std::max(eyeSizes[0].y, eyeSizes[1].y)); - - _frameIndex = 0; - - if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, - ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { - qFatal("Could not attach to sensor device"); - } - - MainWindowOpenGLDisplayPlugin::activate(); -} - -uvec2 OculusBaseDisplayPlugin::getRecommendedRenderSize() const { - return _desiredFramebufferSize; -} - -void OculusBaseDisplayPlugin::preRender() { - ovrHmd_GetEyePoses(_hmd, _frameIndex, _eyeOffsets, _eyePoses, nullptr); -} - -glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { - return _eyeProjections[eye]; -} - -glm::mat4 OculusBaseDisplayPlugin::getModelview(Eye eye, const glm::mat4& baseModelview) const { - return baseModelview * toGlm(_eyePoses[eye]); -} - -void OculusBaseDisplayPlugin::resetSensors() { - ovrHmd_RecenterPose(_hmd); -} - -glm::mat4 OculusBaseDisplayPlugin::getEyePose(Eye eye) const { - return toGlm(_eyePoses[eye]); -} - -// Should NOT be used for rendering as this will mess up timewarp. Use the getModelview() method above for -// any use of head poses for rendering, ensuring you use the correct eye -glm::mat4 OculusBaseDisplayPlugin::getHeadPose() const { - ovrTrackingState state = ovrHmd_GetTrackingState(_hmd, 0.0f); - return toGlm(state.HeadPose.ThePose); -} diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h deleted file mode 100644 index 401ee6579a..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#pragma once - -#include "../MainWindowOpenGLDisplayPlugin.h" - -class OculusBaseDisplayPlugin : public MainWindowOpenGLDisplayPlugin { -public: - OculusBaseDisplayPlugin(); - // Stereo specific methods - virtual bool isHmd() const override { return true; } - virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; - virtual void activate() override; - virtual void preRender() override; - virtual glm::uvec2 getRecommendedRenderSize() const override; - virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } - virtual void resetSensors() override; - virtual glm::mat4 getEyePose(Eye eye) const override; - virtual glm::mat4 getHeadPose() const override; -protected: - float _ipd; -}; diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp similarity index 59% rename from libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp rename to libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index 6ed8977f47..84d67f038a 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Oculus_0_6_DisplayPlugin.h" +#include "OculusDisplayPlugin.h" #include @@ -15,9 +15,7 @@ #include #include #include - -#include - +#include #include #include @@ -27,18 +25,34 @@ #include #include +#include #include "OculusHelpers.h" -using namespace Oculus; -#if (OVR_MAJOR_VERSION == 6) -SwapFboPtr _sceneFbo; -MirrorFboPtr _mirrorFbo; -ovrLayerEyeFov _sceneLayer; +#if (OVR_MAJOR_VERSION == 6) +#define ovr_Create ovrHmd_Create +#define ovr_CreateSwapTextureSetGL ovrHmd_CreateSwapTextureSetGL +#define ovr_CreateMirrorTextureGL ovrHmd_CreateMirrorTextureGL +#define ovr_Destroy ovrHmd_Destroy +#define ovr_DestroySwapTextureSet ovrHmd_DestroySwapTextureSet +#define ovr_DestroyMirrorTexture ovrHmd_DestroyMirrorTexture +#define ovr_GetFloat ovrHmd_GetFloat +#define ovr_GetFovTextureSize ovrHmd_GetFovTextureSize +#define ovr_GetFrameTiming ovrHmd_GetFrameTiming +#define ovr_GetTrackingState ovrHmd_GetTrackingState +#define ovr_GetRenderDesc ovrHmd_GetRenderDesc +#define ovr_RecenterPose ovrHmd_RecenterPose +#define ovr_SubmitFrame ovrHmd_SubmitFrame +#define ovr_ConfigureTracking ovrHmd_ConfigureTracking + +#define ovr_GetHmdDesc(X) *X +#endif + +#if (OVR_MAJOR_VERSION >= 6) // A base class for FBO wrappers that need to use the Oculus C -// API to manage textures via ovrHmd_CreateSwapTextureSetGL, -// ovrHmd_CreateMirrorTextureGL, etc +// API to manage textures via ovr_CreateSwapTextureSetGL, +// ovr_CreateMirrorTextureGL, etc template struct RiftFramebufferWrapper : public FramebufferWrapper { ovrHmd hmd; @@ -73,7 +87,7 @@ struct SwapFramebufferWrapper : public RiftFramebufferWrapper { virtual ~MirrorFramebufferWrapper() { if (color) { - ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); + ovr_DestroyMirrorTexture(hmd, (ovrTexture*)color); color = nullptr; } } @@ -135,10 +149,10 @@ struct MirrorFramebufferWrapper : public RiftFramebufferWrapper { private: void initColor() override { if (color) { - ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); + ovr_DestroyMirrorTexture(hmd, (ovrTexture*)color); color = nullptr; } - ovrResult result = ovrHmd_CreateMirrorTextureGL(hmd, GL_RGBA, size.x, size.y, (ovrTexture**)&color); + ovrResult result = ovr_CreateMirrorTextureGL(hmd, GL_RGBA, size.x, size.y, (ovrTexture**)&color); Q_ASSERT(OVR_SUCCESS(result)); } @@ -149,52 +163,128 @@ private: } }; + #endif +const QString OculusDisplayPlugin::NAME("Oculus Rift"); -const QString Oculus_0_6_DisplayPlugin::NAME("Oculus Rift"); +uvec2 OculusDisplayPlugin::getRecommendedRenderSize() const { + return _desiredFramebufferSize; +} -const QString & Oculus_0_6_DisplayPlugin::getName() const { +void OculusDisplayPlugin::preRender() { +#if (OVR_MAJOR_VERSION >= 6) + ovrFrameTiming ftiming = ovr_GetFrameTiming(_hmd, _frameIndex); + _trackingState = ovr_GetTrackingState(_hmd, ftiming.DisplayMidpointSeconds); + ovr_CalcEyePoses(_trackingState.HeadPose.ThePose, _eyeOffsets, _eyePoses); +#endif +} + +glm::mat4 OculusDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; +} + +void OculusDisplayPlugin::resetSensors() { +#if (OVR_MAJOR_VERSION >= 6) + ovr_RecenterPose(_hmd); +#endif +} + +glm::mat4 OculusDisplayPlugin::getEyePose(Eye eye) const { + return toGlm(_eyePoses[eye]); +} + +glm::mat4 OculusDisplayPlugin::getHeadPose() const { + return toGlm(_trackingState.HeadPose.ThePose); +} + +const QString & OculusDisplayPlugin::getName() const { return NAME; } -bool Oculus_0_6_DisplayPlugin::isSupported() const { -#if (OVR_MAJOR_VERSION == 6) - if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { - return false; - } - bool result = false; - if (ovrHmd_Detect() > 0) { - result = true; - } - ovr_Shutdown(); - return result; +bool OculusDisplayPlugin::isSupported() const { +#if (OVR_MAJOR_VERSION >= 6) + return true; #else return false; #endif } +void OculusDisplayPlugin::init() { + if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { + qFatal("Could not init OVR"); + } +} -#if (OVR_MAJOR_VERSION == 6) -ovrLayerEyeFov& getSceneLayer() { +void OculusDisplayPlugin::deinit() { + ovr_Shutdown(); +} + +#if (OVR_MAJOR_VERSION >= 6) +ovrLayerEyeFov& OculusDisplayPlugin::getSceneLayer() { return _sceneLayer; } #endif //static gpu::TexturePointer _texture; -void Oculus_0_6_DisplayPlugin::activate() { -#if (OVR_MAJOR_VERSION == 6) +void OculusDisplayPlugin::activate() { +#if (OVR_MAJOR_VERSION >= 6) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { Q_ASSERT(false); qFatal("Failed to Initialize SDK"); } - if (!OVR_SUCCESS(ovrHmd_Create(0, &_hmd))) { + +// CONTAINER->getPrimarySurface()->makeCurrent(); +#if (OVR_MAJOR_VERSION == 6) + if (!OVR_SUCCESS(ovr_Create(0, &_hmd))) { +#elif (OVR_MAJOR_VERSION == 7) + if (!OVR_SUCCESS(ovr_Create(&_hmd, &_luid))) { +#endif Q_ASSERT(false); qFatal("Failed to acquire HMD"); } - OculusBaseDisplayPlugin::activate(); + _hmdDesc = ovr_GetHmdDesc(_hmd); + + _ipd = ovr_GetFloat(_hmd, OVR_KEY_IPD, _ipd); + + glm::uvec2 eyeSizes[2]; + ovr_for_each_eye([&](ovrEyeType eye) { + _eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye]; + ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_hmd, eye, _eyeFovs[eye]); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); + _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + _eyeOffsets[eye] = erd.HmdToEyeViewOffset; + eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); + }); + ovrFovPort combined = _eyeFovs[Left]; + combined.LeftTan = std::max(_eyeFovs[Left].LeftTan, _eyeFovs[Right].LeftTan); + combined.RightTan = std::max(_eyeFovs[Left].RightTan, _eyeFovs[Right].RightTan); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(combined, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[Mono] = toGlm(ovrPerspectiveProjection); + + + + _desiredFramebufferSize = uvec2( + eyeSizes[0].x + eyeSizes[1].x, + std::max(eyeSizes[0].y, eyeSizes[1].y)); + + _frameIndex = 0; + + if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd, + ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { + qFatal("Could not attach to sensor device"); + } + + WindowOpenGLDisplayPlugin::activate(); // Parent class relies on our _hmd intialization, so it must come after that. ovrLayerEyeFov& sceneLayer = getSceneLayer(); @@ -203,7 +293,7 @@ void Oculus_0_6_DisplayPlugin::activate() { sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; ovr_for_each_eye([&](ovrEyeType eye) { ovrFovPort & fov = sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov; - ovrSizei & size = sceneLayer.Viewport[eye].Size = ovrHmd_GetFovTextureSize(_hmd, eye, fov, 1.0f); + ovrSizei & size = sceneLayer.Viewport[eye].Size = ovr_GetFovTextureSize(_hmd, eye, fov, 1.0f); sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 }; }); // We're rendering both eyes to the same texture, so only one of the @@ -214,17 +304,16 @@ void Oculus_0_6_DisplayPlugin::activate() { PerformanceTimer::setActive(true); - if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, + if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd, ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { qFatal("Could not attach to sensor device"); } #endif } -void Oculus_0_6_DisplayPlugin::customizeContext() { -#if (OVR_MAJOR_VERSION == 6) - OculusBaseDisplayPlugin::customizeContext(); - +void OculusDisplayPlugin::customizeContext() { + WindowOpenGLDisplayPlugin::customizeContext(); +#if (OVR_MAJOR_VERSION >= 6) //_texture = DependencyManager::get()-> // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png"); uvec2 mirrorSize = toGlm(_window->geometry().size()); @@ -236,24 +325,29 @@ void Oculus_0_6_DisplayPlugin::customizeContext() { #endif } -void Oculus_0_6_DisplayPlugin::deactivate() { -#if (OVR_MAJOR_VERSION == 6) +void OculusDisplayPlugin::deactivate() { +#if (OVR_MAJOR_VERSION >= 6) makeCurrent(); _sceneFbo.reset(); _mirrorFbo.reset(); doneCurrent(); PerformanceTimer::setActive(false); - OculusBaseDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); - ovrHmd_Destroy(_hmd); + ovr_Destroy(_hmd); _hmd = nullptr; ovr_Shutdown(); #endif } -void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { -#if (OVR_MAJOR_VERSION == 6) +void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { + static bool inDisplay = false; + if (inDisplay) { + return; + } + inDisplay = true; +#if (OVR_MAJOR_VERSION >= 6) using namespace oglplus; // Need to make sure only the display plugin is responsible for // controlling vsync @@ -263,6 +357,7 @@ void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc auto size = _sceneFbo->size; Context::Viewport(size.x, size.y); glBindTexture(GL_TEXTURE_2D, finalTexture); + GLenum err = glGetError(); drawUnitQuad(); }); @@ -280,17 +375,25 @@ void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc the UI visible in the output window (unlikely). This should be done before _sceneFbo->Increment or we're be using the wrong texture */ - //_sceneFbo->Bound(GL_READ_FRAMEBUFFER, [&] { - // glBlitFramebuffer( - // 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, - // 0, 0, windowSize.x, _mirrorFbo.y, - // GL_COLOR_BUFFER_BIT, GL_NEAREST); - //}); + _sceneFbo->Bound(Framebuffer::Target::Read, [&] { + glBlitFramebuffer( + 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, + 0, 0, windowSize.x, windowSize.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + }); { PerformanceTimer("OculusSubmit"); + ovrViewScaleDesc viewScaleDesc; + viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; + viewScaleDesc.HmdToEyeViewOffset[0] = _eyeOffsets[0]; + viewScaleDesc.HmdToEyeViewOffset[1] = _eyeOffsets[1]; + ovrLayerHeader* layers = &sceneLayer.Header; - ovrResult result = ovrHmd_SubmitFrame(_hmd, _frameIndex, nullptr, &layers, 1); + ovrResult result = ovr_SubmitFrame(_hmd, 0, &viewScaleDesc, &layers, 1); + if (!OVR_SUCCESS(result)) { + qDebug() << result; + } } _sceneFbo->Increment(); @@ -299,21 +402,22 @@ void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc will contain the post-distorted and fully composited scene regardless of how many layers we send. */ - auto mirrorSize = _mirrorFbo->size; - _mirrorFbo->Bound(Framebuffer::Target::Read, [&] { - Context::BlitFramebuffer( - 0, mirrorSize.y, mirrorSize.x, 0, - 0, 0, windowSize.x, windowSize.y, - BufferSelectBit::ColorBuffer, BlitFilter::Nearest); - }); + //auto mirrorSize = _mirrorFbo->size; + //_mirrorFbo->Bound(Framebuffer::Target::Read, [&] { + // Context::BlitFramebuffer( + // 0, mirrorSize.y, mirrorSize.x, 0, + // 0, 0, windowSize.x, windowSize.y, + // BufferSelectBit::ColorBuffer, BlitFilter::Nearest); + //}); ++_frameIndex; #endif + inDisplay = false; } // Pass input events on to the application -bool Oculus_0_6_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { -#if (OVR_MAJOR_VERSION == 6) +bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { +#if (OVR_MAJOR_VERSION >= 6) if (event->type() == QEvent::Resize) { QResizeEvent* resizeEvent = static_cast(event); qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height(); @@ -323,7 +427,7 @@ bool Oculus_0_6_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { doneCurrent(); } #endif - return OculusBaseDisplayPlugin::eventFilter(receiver, event); + return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); } /* @@ -331,8 +435,8 @@ bool Oculus_0_6_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { However, it should only be done if we can reliably disable v-sync on the mirror surface, otherwise the swapbuffer delay will interefere with the framerate of the headset */ -void Oculus_0_6_DisplayPlugin::finishFrame() { - swapBuffers(); +void OculusDisplayPlugin::finishFrame() { + //swapBuffers(); doneCurrent(); }; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h new file mode 100644 index 0000000000..75173fd2bd --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h @@ -0,0 +1,78 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "../WindowOpenGLDisplayPlugin.h" + +#include + +#include + +class OffscreenGlCanvas; +struct SwapFramebufferWrapper; +struct MirrorFramebufferWrapper; + +using SwapFboPtr = QSharedPointer; +using MirrorFboPtr = QSharedPointer; + +class OculusDisplayPlugin : public WindowOpenGLDisplayPlugin { +public: + virtual bool isSupported() const override; + virtual const QString & getName() const override; + + virtual void init() override; + virtual void deinit() override; + + virtual void activate() override; + virtual void deactivate() override; + + virtual bool eventFilter(QObject* receiver, QEvent* event) override; + + // Stereo specific methods + virtual bool isHmd() const override { return true; } + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } + virtual void resetSensors() override; + virtual glm::mat4 getEyePose(Eye eye) const override; + virtual glm::mat4 getHeadPose() const override; + +protected: + virtual void preRender() override; + virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + virtual void customizeContext() override; + // Do not perform swap in finish + virtual void finishFrame() override; + +private: + static const QString NAME; + + ovrHmd _hmd; + float _ipd{ OVR_DEFAULT_IPD }; + unsigned int _frameIndex; + ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrPosef _eyePoses[2]; + ovrVector3f _eyeOffsets[2]; + ovrFovPort _eyeFovs[2]; + mat4 _eyeProjections[3]; + mat4 _compositeEyeProjections[2]; + uvec2 _desiredFramebufferSize; + ovrTrackingState _trackingState; + +#if (OVR_MAJOR_VERSION >= 6) + ovrLayerEyeFov& getSceneLayer(); + ovrHmdDesc _hmdDesc; + SwapFboPtr _sceneFbo; + MirrorFboPtr _mirrorFbo; + ovrLayerEyeFov _sceneLayer; +#endif +#if (OVR_MAJOR_VERSION == 7) + ovrGraphicsLuid _luid; +#endif +}; + diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h index 31b7c246af..df0a6c5228 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h @@ -7,7 +7,7 @@ // #pragma once -#include +#include #include #include #include @@ -79,14 +79,3 @@ inline ovrQuatf ovrFromGlm(const glm::quat & q) { return{ q.x, q.y, q.z, q.w }; } -namespace Oculus { - extern ovrHmd _hmd; - extern unsigned int _frameIndex; - extern ovrEyeRenderDesc _eyeRenderDescs[2]; - extern ovrPosef _eyePoses[2]; - extern ovrVector3f _eyeOffsets[2]; - extern ovrFovPort _eyeFovs[2]; - extern mat4 _eyeProjections[2]; - extern mat4 _compositeEyeProjections[2]; - extern uvec2 _desiredFramebufferSize; -} diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp similarity index 55% rename from libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp rename to libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp index 86ee3b41f2..2c5af1a52b 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Oculus_0_5_DisplayPlugin.h" +#include "OculusLegacyDisplayPlugin.h" #include @@ -19,34 +19,66 @@ #include #include - -#include - #include #include +#include #include "plugins/PluginContainer.h" #include "OculusHelpers.h" -using namespace Oculus; -ovrTexture _eyeTextures[2]; -int _hmdScreen{ -1 }; -bool _hswDismissed{ false }; - -DisplayPlugin* makeOculusDisplayPlugin() { - return new Oculus_0_5_DisplayPlugin(); -} - using namespace oglplus; -const QString Oculus_0_5_DisplayPlugin::NAME("Oculus Rift (0.5)"); +const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift (0.5)"); -const QString & Oculus_0_5_DisplayPlugin::getName() const { +const QString & OculusLegacyDisplayPlugin::getName() const { return NAME; } +OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() : _ipd(OVR_DEFAULT_IPD) { +} -bool Oculus_0_5_DisplayPlugin::isSupported() const { +uvec2 OculusLegacyDisplayPlugin::getRecommendedRenderSize() const { + return _desiredFramebufferSize; +} + +void OculusLegacyDisplayPlugin::preRender() { +#if (OVR_MAJOR_VERSION == 5) + ovrHmd_GetEyePoses(_hmd, _frameIndex, _eyeOffsets, _eyePoses, &_trackingState); + ovrHmd_BeginFrame(_hmd, _frameIndex); +#endif + WindowOpenGLDisplayPlugin::preRender(); +} + +glm::mat4 OculusLegacyDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; +} + +void OculusLegacyDisplayPlugin::resetSensors() { +#if (OVR_MAJOR_VERSION == 5) + ovrHmd_RecenterPose(_hmd); +#endif +} + +glm::mat4 OculusLegacyDisplayPlugin::getEyePose(Eye eye) const { +#if (OVR_MAJOR_VERSION == 5) + return toGlm(_eyePoses[eye]); +#else + return WindowOpenGLDisplayPlugin::getEyePose(eye); +#endif +} + +// Should NOT be used for rendering as this will mess up timewarp. Use the getModelview() method above for +// any use of head poses for rendering, ensuring you use the correct eye +glm::mat4 OculusLegacyDisplayPlugin::getHeadPose() const { +#if (OVR_MAJOR_VERSION == 5) + return toGlm(_trackingState.HeadPose.ThePose); +#else + return WindowOpenGLDisplayPlugin::getHeadPose(); +#endif +} + + +bool OculusLegacyDisplayPlugin::isSupported() const { #if (OVR_MAJOR_VERSION == 5) if (!ovr_Initialize(nullptr)) { return false; @@ -77,7 +109,7 @@ bool Oculus_0_5_DisplayPlugin::isSupported() const { #endif } -void Oculus_0_5_DisplayPlugin::activate() { +void OculusLegacyDisplayPlugin::activate() { #if (OVR_MAJOR_VERSION == 5) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { Q_ASSERT(false); @@ -89,7 +121,34 @@ void Oculus_0_5_DisplayPlugin::activate() { qFatal("Failed to acquire HMD"); } - OculusBaseDisplayPlugin::activate(); + glm::uvec2 eyeSizes[2]; + ovr_for_each_eye([&](ovrEyeType eye) { + _eyeFovs[eye] = _hmd->MaxEyeFov[eye]; + ovrEyeRenderDesc erd = _eyeRenderDescs[eye] = ovrHmd_GetRenderDesc(_hmd, eye, _eyeFovs[eye]); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); + _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + _eyeOffsets[eye] = erd.HmdToEyeViewOffset; + eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); + }); + _desiredFramebufferSize = uvec2( + eyeSizes[0].x + eyeSizes[1].x, + std::max(eyeSizes[0].y, eyeSizes[1].y)); + + _frameIndex = 0; + + if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, + ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { + qFatal("Could not attach to sensor device"); + } + + WindowOpenGLDisplayPlugin::activate(); + int screen = getHmdScreen(); if (screen != -1) { CONTAINER->setFullscreen(qApp->screens()[screen]); @@ -118,17 +177,16 @@ void Oculus_0_5_DisplayPlugin::activate() { } }); - ovrEyeRenderDesc _eyeRenderDescs[ovrEye_Count]; ovrBool result = ovrHmd_ConfigureRendering(_hmd, &config.Config, distortionCaps, _eyeFovs, _eyeRenderDescs); Q_ASSERT(result); #endif } -void Oculus_0_5_DisplayPlugin::deactivate() { +void OculusLegacyDisplayPlugin::deactivate() { #if (OVR_MAJOR_VERSION == 5) _window->removeEventFilter(this); - OculusBaseDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); QScreen* riftScreen = nullptr; if (_hmdScreen >= 0) { @@ -142,18 +200,11 @@ void Oculus_0_5_DisplayPlugin::deactivate() { #endif } -void Oculus_0_5_DisplayPlugin::preRender() { -#if (OVR_MAJOR_VERSION == 5) - OculusBaseDisplayPlugin::preRender(); - ovrHmd_BeginFrame(_hmd, _frameIndex); -#endif -} - -void Oculus_0_5_DisplayPlugin::preDisplay() { +void OculusLegacyDisplayPlugin::preDisplay() { _window->makeCurrent(); } -void Oculus_0_5_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { +void OculusLegacyDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { ++_frameIndex; #if (OVR_MAJOR_VERSION == 5) ovr_for_each_eye([&](ovrEyeType eye) { @@ -164,7 +215,7 @@ void Oculus_0_5_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc } // Pass input events on to the application -bool Oculus_0_5_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { +bool OculusLegacyDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { #if (OVR_MAJOR_VERSION == 5) if (!_hswDismissed && (event->type() == QEvent::KeyPress)) { static ovrHSWDisplayState hswState; @@ -176,17 +227,19 @@ bool Oculus_0_5_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { } } #endif - return OculusBaseDisplayPlugin::eventFilter(receiver, event); + return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); } // FIXME mirroring tot he main window is diffucult on OSX because it requires that we // trigger a swap, which causes the client to wait for the v-sync of the main screen running // at 60 Hz. This would introduce judder. Perhaps we can push mirroring to a separate // thread -void Oculus_0_5_DisplayPlugin::finishFrame() { +// FIXME If we move to the 'batch rendering on a different thread' we can possibly do this. +// however, we need to make sure it doesn't block the event handling. +void OculusLegacyDisplayPlugin::finishFrame() { _window->doneCurrent(); }; -int Oculus_0_5_DisplayPlugin::getHmdScreen() const { +int OculusLegacyDisplayPlugin::getHmdScreen() const { return _hmdScreen; } diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h new file mode 100644 index 0000000000..ce91289cb0 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h @@ -0,0 +1,63 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "../WindowOpenGLDisplayPlugin.h" + +#include + +#include + +class OculusLegacyDisplayPlugin : public WindowOpenGLDisplayPlugin { +public: + OculusLegacyDisplayPlugin(); + virtual bool isSupported() const override; + virtual const QString & getName() const override; + + virtual void activate() override; + virtual void deactivate() override; + + virtual bool eventFilter(QObject* receiver, QEvent* event) override; + virtual int getHmdScreen() const override; + + // Stereo specific methods + virtual bool isHmd() const override { return true; } + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } + virtual void resetSensors() override; + virtual glm::mat4 getEyePose(Eye eye) const override; + virtual glm::mat4 getHeadPose() const override; + +protected: + virtual void preRender() override; + virtual void preDisplay() override; + virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + // Do not perform swap in finish + virtual void finishFrame() override; + +private: + static const QString NAME; + + float _ipd{ OVR_DEFAULT_IPD }; + ovrHmd _hmd; + unsigned int _frameIndex; + ovrTrackingState _trackingState; + ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrPosef _eyePoses[2]; + ovrVector3f _eyeOffsets[2]; + ovrFovPort _eyeFovs[2]; + mat4 _eyeProjections[2]; + mat4 _compositeEyeProjections[2]; + uvec2 _desiredFramebufferSize; + ovrTexture _eyeTextures[2]; + mutable int _hmdScreen{ -1 }; + bool _hswDismissed{ false }; +}; + + diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h deleted file mode 100644 index b539d07fb0..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#pragma once - -#include "OculusBaseDisplayPlugin.h" - -#include - -class Oculus_0_5_DisplayPlugin : public OculusBaseDisplayPlugin { -public: - virtual bool isSupported() const override; - virtual const QString & getName() const override; - - virtual void activate() override; - virtual void deactivate() override; - - virtual bool eventFilter(QObject* receiver, QEvent* event) override; - - virtual int getHmdScreen() const override; - -protected: - virtual void preRender() override; - virtual void preDisplay() override; - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; - // Do not perform swap in finish - virtual void finishFrame() override; - -private: - static const QString NAME; -}; - - diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h deleted file mode 100644 index 0fde5e76b3..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#pragma once - -#include "OculusBaseDisplayPlugin.h" - -#include - -class OffscreenGlCanvas; -struct SwapFramebufferWrapper; -struct MirrorFramebufferWrapper; - -using SwapFboPtr = QSharedPointer; -using MirrorFboPtr = QSharedPointer; - -class Oculus_0_6_DisplayPlugin : public OculusBaseDisplayPlugin { -public: - virtual bool isSupported() const override; - virtual const QString & getName() const override; - - virtual void activate() override; - virtual void deactivate() override; - - - virtual bool eventFilter(QObject* receiver, QEvent* event) override; - -protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; - virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; - -private: - static const QString NAME; -}; - diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp index 181546d428..1a5aa2f437 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -128,7 +128,7 @@ void OpenVrDisplayPlugin::activate() { delete[] buffer; } Q_ASSERT(unSize <= 1); - MainWindowOpenGLDisplayPlugin::activate(); + WindowOpenGLDisplayPlugin::activate(); } void OpenVrDisplayPlugin::deactivate() { @@ -141,7 +141,7 @@ void OpenVrDisplayPlugin::deactivate() { _hmd = nullptr; } _compositor = nullptr; - MainWindowOpenGLDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); } uvec2 OpenVrDisplayPlugin::getRecommendedRenderSize() const { @@ -152,16 +152,12 @@ mat4 OpenVrDisplayPlugin::getProjection(Eye eye, const mat4& baseProjection) con return _eyesData[eye]._projectionMatrix; } -glm::mat4 OpenVrDisplayPlugin::getModelview(Eye eye, const mat4& baseModelview) const { - return baseModelview * getEyePose(eye); -} - void OpenVrDisplayPlugin::resetSensors() { _sensorResetMat = glm::inverse(cancelOutRollAndPitch(_trackedDevicePoseMat4[0])); } glm::mat4 OpenVrDisplayPlugin::getEyePose(Eye eye) const { - return getHeadPose() * _eyesData[eye]._eyeOffset; + return _eyesData[eye]._eyeOffset * getHeadPose(); } glm::mat4 OpenVrDisplayPlugin::getHeadPose() const { @@ -169,7 +165,7 @@ glm::mat4 OpenVrDisplayPlugin::getHeadPose() const { } void OpenVrDisplayPlugin::customizeContext() { - MainWindowOpenGLDisplayPlugin::customizeContext(); + WindowOpenGLDisplayPlugin::customizeContext(); } void OpenVrDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h index 608a869341..afe024e72b 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h @@ -11,9 +11,9 @@ #if defined(Q_OS_WIN) -#include "../MainWindowOpenGLDisplayPlugin.h" +#include "../WindowOpenGLDisplayPlugin.h" -class OpenVrDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +class OpenVrDisplayPlugin : public WindowOpenGLDisplayPlugin { public: virtual bool isSupported() const override; virtual const QString & getName() const override; @@ -27,7 +27,6 @@ public: // Stereo specific methods virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; virtual void resetSensors() override; virtual glm::mat4 getEyePose(Eye eye) const override; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 3d5f73bfa8..104d74097c 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -42,12 +42,12 @@ glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProje return glm::translate(baseProjection, vec3(frustumshift, 0, 0)); } -glm::mat4 StereoDisplayPlugin::getModelview(Eye eye, const glm::mat4& baseModelview) const { +glm::mat4 StereoDisplayPlugin::getEyePose(Eye eye) const { float modelviewShift = HALF_DEFAULT_IPD; if (eye == Left) { modelviewShift = -modelviewShift; } - return baseModelview * glm::translate(mat4(), vec3(modelviewShift, 0, 0)); + return glm::translate(mat4(), vec3(modelviewShift, 0, 0)); } void StereoDisplayPlugin::activate() { @@ -57,11 +57,3 @@ void StereoDisplayPlugin::activate() { //CONTAINER->setFullscreen(qApp->primaryScreen()); // FIXME Add menu items } - -glm::vec3 StereoDisplayPlugin::getEyeOffset(Eye eye) const { - glm::vec3 result(_ipd / 2.0f, 0, 0); - if (eye == Eye::Right) { - result *= -1.0f; - } - return result; -} diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index 77b1141aed..b0f0414de2 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -7,9 +7,9 @@ // #pragma once -#include "../MainWindowOpenGLDisplayPlugin.h" +#include "../WindowOpenGLDisplayPlugin.h" -class StereoDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +class StereoDisplayPlugin : public WindowOpenGLDisplayPlugin { Q_OBJECT public: StereoDisplayPlugin(); @@ -19,8 +19,7 @@ public: virtual void activate() override; virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; - virtual glm::vec3 getEyeOffset(Eye eye) const override; + virtual glm::mat4 getEyePose(Eye eye) const override; protected: float _ipd{ 0.064f }; From 62e8ec3fdfc47dea34dd9adb62c0307570d6a591 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 20 Aug 2015 15:09:52 -0700 Subject: [PATCH 106/170] add fullscreen menu item and rename side by side stereo plugin --- .../Basic2DWindowOpenGLDisplayPlugin.cpp | 4 ++-- .../stereo/SideBySideStereoDisplayPlugin.cpp | 24 ++++++++++++++++++- .../stereo/SideBySideStereoDisplayPlugin.h | 6 ++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 14ba868275..018a09ff7e 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -13,8 +13,8 @@ const QString Basic2DWindowOpenGLDisplayPlugin::NAME("2D Display"); -const QString MENU_PATH = "Display"; -const QString FULLSCREEN = "Fullscreen"; +static const QString MENU_PATH = "Display"; +static const QString FULLSCREEN = "Fullscreen"; const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { return NAME; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp index 72ef5249a8..829385b209 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -10,14 +10,19 @@ #include #include +#include #include #include #include #include +#include -const QString SideBySideStereoDisplayPlugin::NAME("Debug Stereo Display"); +const QString SideBySideStereoDisplayPlugin::NAME("3D TV - Side by Side Stereo"); + +static const QString MENU_PATH = "Display"; +static const QString FULLSCREEN = "Fullscreen"; const QString & SideBySideStereoDisplayPlugin::getName() const { return NAME; @@ -26,3 +31,20 @@ const QString & SideBySideStereoDisplayPlugin::getName() const { SideBySideStereoDisplayPlugin::SideBySideStereoDisplayPlugin() { } +void SideBySideStereoDisplayPlugin::activate() { + CONTAINER->addMenu(MENU_PATH); + CONTAINER->addMenuItem(MENU_PATH, FULLSCREEN, + [this](bool clicked) { + if (clicked) { + CONTAINER->setFullscreen(getFullscreenTarget()); + } else { + CONTAINER->unsetFullscreen(); + } + }, true, false); + StereoDisplayPlugin::activate(); +} + +// FIXME target the screen the window is currently on +QScreen* SideBySideStereoDisplayPlugin::getFullscreenTarget() { + return qApp->primaryScreen(); +} diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h index ead9ea7dc4..3a764d9f4e 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h @@ -9,11 +9,15 @@ #include "StereoDisplayPlugin.h" +class QScreen; + class SideBySideStereoDisplayPlugin : public StereoDisplayPlugin { Q_OBJECT public: SideBySideStereoDisplayPlugin(); - virtual const QString & getName() const override; + virtual const QString& getName() const override; + virtual void activate() override; private: + QScreen* getFullscreenTarget(); static const QString NAME; }; From 476e5edb3210bb76d43e9498526f468317a9459f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 20 Aug 2015 15:47:31 -0700 Subject: [PATCH 107/170] Fixing HMD wobble --- interface/src/Application.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 374963e9b6..c3773c3b73 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1123,10 +1123,18 @@ void Application::paintGL() { mat4 eyeViews[2]; mat4 eyeProjections[2]; auto baseProjection = renderArgs._viewFrustum->getProjection(); - // FIXME we don't need to set these every frame, - // only when the display plugin changes + // FIXME we probably don't need to set the projection matrix every frame, + // only when the display plugin changes (or in non-HMD modes when the user + // changes the FOV manually, which right now I don't think they can. for_each_eye([&](Eye eye) { - eyeViews[eye] = displayPlugin->getView(eye, mat4()); + // For providing the stereo eye views, the HMD head pose has already been + // applied to the avatar, so we need to get the difference between the head + // pose applied to the avatar and the per eye pose, and use THAT as + // the per-eye stereo matrix adjustment. + mat4 eyePose = displayPlugin->getEyePose(eye); + mat4 headPose = displayPlugin->getHeadPose(); + mat4 eyeView = glm::inverse(eyePose) * headPose; + eyeViews[eye] = eyeView; eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection); }); renderArgs._context->setStereoProjections(eyeProjections); From cc037c8828f2a6b84e0bf90e0e24ca280d2ef5a6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 20 Aug 2015 16:20:34 -0700 Subject: [PATCH 108/170] Fix culling calculations in HMD with new stereo mechanism --- interface/src/Application.cpp | 11 ++++++++++- .../oculus/OculusLegacyDisplayPlugin.cpp | 7 +++++++ .../oculus/OculusLegacyDisplayPlugin.h | 2 +- .../display-plugins/openvr/OpenVrDisplayPlugin.cpp | 4 ++++ .../display-plugins/stereo/StereoDisplayPlugin.cpp | 2 ++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c3773c3b73..0c3bbd8d34 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1118,7 +1118,16 @@ void Application::paintGL() { renderArgs._viewport = gpu::Vec4i(0, 0, size.width(), size.height()); if (displayPlugin->isStereo()) { - //_myCamera.setProjection(displayPlugin->getProjection(Mono, _myCamera.getProjection())); + // Stereo modes will typically have a larger projection matrix overall, + // so we ask for the 'mono' projection matrix, which for stereo and HMD + // plugins will imply the combined projection for both eyes. + // + // This is properly implemented for the Oculus plugins, but for OpenVR + // and Stereo displays I'm not sure how to get / calculate it, so we're + // just relying on the left FOV in each case and hoping that the + // overall culling margin of error doesn't cause popping in the + // right eye. There are FIXMEs in the relevant plugins + _myCamera.setProjection(displayPlugin->getProjection(Mono, _myCamera.getProjection())); renderArgs._context->enableStereo(true); mat4 eyeViews[2]; mat4 eyeProjections[2]; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp index 2c5af1a52b..e823f456ce 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp @@ -136,6 +136,13 @@ void OculusLegacyDisplayPlugin::activate() { _eyeOffsets[eye] = erd.HmdToEyeViewOffset; eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); }); + ovrFovPort combined = _eyeFovs[Left]; + combined.LeftTan = std::max(_eyeFovs[Left].LeftTan, _eyeFovs[Right].LeftTan); + combined.RightTan = std::max(_eyeFovs[Left].RightTan, _eyeFovs[Right].RightTan); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(combined, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[Mono] = toGlm(ovrPerspectiveProjection); + _desiredFramebufferSize = uvec2( eyeSizes[0].x + eyeSizes[1].x, std::max(eyeSizes[0].y, eyeSizes[1].y)); diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h index ce91289cb0..219b6c54b3 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h @@ -52,7 +52,7 @@ private: ovrPosef _eyePoses[2]; ovrVector3f _eyeOffsets[2]; ovrFovPort _eyeFovs[2]; - mat4 _eyeProjections[2]; + mat4 _eyeProjections[3]; mat4 _compositeEyeProjections[2]; uvec2 _desiredFramebufferSize; ovrTexture _eyeTextures[2]; diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp index 1a5aa2f437..1e3e7699f4 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -149,6 +149,10 @@ uvec2 OpenVrDisplayPlugin::getRecommendedRenderSize() const { } mat4 OpenVrDisplayPlugin::getProjection(Eye eye, const mat4& baseProjection) const { + // FIXME hack to ensure that we don't crash trying to get the combined matrix + if (eye == Mono) { + eye = Left; + } return _eyesData[eye]._projectionMatrix; } diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 104d74097c..20613d6a2c 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -29,6 +29,8 @@ const float DEFAULT_IPD = 0.064f; const float HALF_DEFAULT_IPD = DEFAULT_IPD / 2.0f; glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { + // FIXME check for mono eye and provide a combined matrix, needed for proper + // culling // Refer to http://www.nvidia.com/content/gtc-2010/pdfs/2010_gtc2010.pdf on creating // stereo projection matrices. Do NOT use "toe-in", use translation. From 75aff2f281053e3bc0e30a5ee6fc6090739cdf31 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 20 Aug 2015 16:39:23 -0700 Subject: [PATCH 109/170] fix atmosphere rendering --- libraries/render-utils/src/Environment.cpp | 24 +++++++++++++++------- libraries/render-utils/src/Environment.h | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/libraries/render-utils/src/Environment.cpp b/libraries/render-utils/src/Environment.cpp index 9cce508946..605f67f957 100644 --- a/libraries/render-utils/src/Environment.cpp +++ b/libraries/render-utils/src/Environment.cpp @@ -92,17 +92,17 @@ void Environment::resetToDefault() { _data[QUuid()][0]; } -void Environment::renderAtmospheres(gpu::Batch& batch, ViewFrustum& camera) { +void Environment::renderAtmospheres(gpu::Batch& batch, ViewFrustum& viewFrustum) { // get the lock for the duration of the call QMutexLocker locker(&_mutex); if (_environmentIsOverridden) { - renderAtmosphere(batch, camera, _overrideData); + renderAtmosphere(batch, viewFrustum, _overrideData); } else { foreach (const ServerData& serverData, _data) { // TODO: do something about EnvironmentData foreach (const EnvironmentData& environmentData, serverData) { - renderAtmosphere(batch, camera, environmentData); + renderAtmosphere(batch, viewFrustum, environmentData); } } } @@ -196,15 +196,25 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3 return found; } -void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& camera, const EnvironmentData& data) { +void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, const EnvironmentData& data) { glm::vec3 center = data.getAtmosphereCenter(); + // transform the model transform to the center of our atmosphere Transform transform; transform.setTranslation(center); batch.setModelTransform(transform); - - glm::vec3 relativeCameraPos = camera.getPosition() - center; + + // Make sure our view and projection transforms are correct for our viewFrustum + Transform viewTransform; + viewFrustum.evalViewTransform(viewTransform); + batch.setViewTransform(viewTransform); + + glm::mat4 projMat; + viewFrustum.evalProjectionMatrix(projMat); + batch.setProjectionTransform(projMat); + + glm::vec3 relativeCameraPos = viewFrustum.getPosition() - center; float height = glm::length(relativeCameraPos); // use the appropriate shader depending on whether we're inside or outside @@ -212,11 +222,11 @@ void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& camera, const if (height < data.getAtmosphereOuterRadius()) { batch.setPipeline(_skyFromAtmosphereProgram); locations = _skyFromAtmosphereUniformLocations; - } else { batch.setPipeline(_skyFromSpaceProgram); locations = _skyFromSpaceUniformLocations; } + // the constants here are from Sean O'Neil's GPU Gems entry // (http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html), GameEngine.cpp diff --git a/libraries/render-utils/src/Environment.h b/libraries/render-utils/src/Environment.h index 5b10f2f1a9..af5a3c3df5 100644 --- a/libraries/render-utils/src/Environment.h +++ b/libraries/render-utils/src/Environment.h @@ -29,7 +29,7 @@ public: void init(); void resetToDefault(); - void renderAtmospheres(gpu::Batch& batch, ViewFrustum& camera); + void renderAtmospheres(gpu::Batch& batch, ViewFrustum& viewFrustum); void override(const EnvironmentData& overrideData) { _overrideData = overrideData; _environmentIsOverridden = true; } void endOverride() { _environmentIsOverridden = false; } @@ -41,7 +41,7 @@ private: bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration); // NOTE: Deprecated - void renderAtmosphere(gpu::Batch& batch, ViewFrustum& camera, const EnvironmentData& data); + void renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, const EnvironmentData& data); bool _initialized; From 1e074ab81c20cd2ef3bf60cb271988a238a9e6aa Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 21 Aug 2015 00:28:43 -0700 Subject: [PATCH 110/170] Remove superfluous clear --- interface/src/Application.cpp | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0c3bbd8d34..301362aca0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1097,26 +1097,11 @@ void Application::paintGL() { // Primary rendering pass auto framebufferCache = DependencyManager::get(); - QSize size = framebufferCache->getFrameBufferSize(); + const QSize size = framebufferCache->getFrameBufferSize(); { PROFILE_RANGE(__FUNCTION__ "/mainRender"); // Viewport is assigned to the size of the framebuffer - QSize size = DependencyManager::get()->getFrameBufferSize(); - renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height()); - - doInBatch(&renderArgs, [&](gpu::Batch& batch) { - auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); - batch.setFramebuffer(primaryFbo); - // clear the normal and specular buffers - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | - gpu::Framebuffer::BUFFER_COLOR1 | - gpu::Framebuffer::BUFFER_COLOR2 | - gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(0), 1), 1.0, 0.0); - }); - - renderArgs._viewport = gpu::Vec4i(0, 0, size.width(), size.height()); + renderArgs._viewport = ivec4(0, 0, size.width(), size.height()); if (displayPlugin->isStereo()) { // Stereo modes will typically have a larger projection matrix overall, // so we ask for the 'mono' projection matrix, which for stereo and HMD @@ -1185,7 +1170,6 @@ void Application::paintGL() { PROFILE_RANGE(__FUNCTION__ "/pluginOutput"); auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0)); - uvec2 finalSize = toGlm(size); // Ensure the rendering context commands are completed when rendering GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); // Ensure the sync object is flushed to the driver thread before releasing the context @@ -1201,7 +1185,7 @@ void Application::paintGL() { { PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); - displayPlugin->display(finalTexture, finalSize); + displayPlugin->display(finalTexture, toGlm(size)); } { From a0552050b08681e939afb36e316499f2a5833d9c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 21 Aug 2015 08:23:18 -0700 Subject: [PATCH 111/170] set vsync on startup --- interface/src/Application.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b9bafbab1a..aa18e602bf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -737,6 +737,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : }); connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); + + setVSyncEnabled(); // make sure VSync is set properly at startup } void Application::aboutToQuit() { From 270463fc4e5ac72648869ec45cca793421d02d91 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 21 Aug 2015 09:29:25 -0700 Subject: [PATCH 112/170] restore new oculus detect code from master --- .../src/display-plugins/oculus/OculusDisplayPlugin.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index 84d67f038a..ff218987ec 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -204,7 +204,15 @@ const QString & OculusDisplayPlugin::getName() const { bool OculusDisplayPlugin::isSupported() const { #if (OVR_MAJOR_VERSION >= 6) - return true; + if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { + return false; + } + bool result = false; + if (ovrHmd_Detect() > 0) { + result = true; + } + ovr_Shutdown(); + return result; #else return false; #endif From b8b0917d9db1c38d58ae4a5be6829f9c1755ae49 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 21 Aug 2015 10:01:11 -0700 Subject: [PATCH 113/170] Attempting to fix judder issues --- interface/src/Application.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 301362aca0..d3b1ce2318 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -993,6 +993,8 @@ void Application::paintGL() { auto displayPlugin = getActiveDisplayPlugin(); displayPlugin->preRender(); _offscreenContext->makeCurrent(); + // update the avatar with a fresh HMD pose + _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); auto lodManager = DependencyManager::get(); @@ -2683,9 +2685,6 @@ void Application::update(float deltaTime) { updateLOD(); updateMouseRay(); // check what's under the mouse and update the mouse voxel - // update the avatar with a fresh HMD pose - _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); - { PerformanceTimer perfTimer("devices"); DeviceTracker::updateAll(); From 55fd847636cbc99a438ecaef7cb5b0d4ee69ed0e Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 21 Aug 2015 19:07:33 +0200 Subject: [PATCH 114/170] removed unused code, renamed UnitTest.ready() to UnitTest.done() --- examples/libraries/unitTest.js | 2 +- examples/utilities/diagnostics/testWebSocket.js | 6 +++--- libraries/script-engine/src/WebSocketClass.cpp | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/libraries/unitTest.js b/examples/libraries/unitTest.js index d65cd780bd..7d5234933f 100644 --- a/examples/libraries/unitTest.js +++ b/examples/libraries/unitTest.js @@ -173,7 +173,7 @@ UnitTest.prototype.raises = function(func, message) { throw new UnthrownException(message); } -UnitTest.prototype.ready = function() { +UnitTest.prototype.done = function() { if (this.timeout !== undefined) { Script.clearTimeout(this.timeoutTimer); this.successCallback(this); diff --git a/examples/utilities/diagnostics/testWebSocket.js b/examples/utilities/diagnostics/testWebSocket.js index 03b9b6b405..af915c3790 100644 --- a/examples/utilities/diagnostics/testWebSocket.js +++ b/examples/utilities/diagnostics/testWebSocket.js @@ -35,7 +35,7 @@ unitTests.addTest("Test default WebSocket values", function(finished) { }); webSocket.onclose = this.registerCallbackFunction(function(event) { _this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED"); - _this.ready(); + _this.done(); }); this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING"); this.assertEquals("blob", webSocket.binaryType, "binaryType should be 'blob'"); @@ -55,7 +55,7 @@ unitTests.addTest("Test WebSocket invalid URL", function(finished) { webSocket.onclose = this.registerCallbackFunction(function(event) { _this.assertEquals(true, hadError, "hadError should be true"); _this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED"); - _this.ready(); + _this.done(); }); this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING"); this.assertEquals(WEBSOCKET_INVALID_URL, webSocket.url, "url should be '" + WEBSOCKET_INVALID_URL + "'"); @@ -78,7 +78,7 @@ unitTests.addTest("Test WebSocketServer with three clients", function(finished) if (respondedClients === NUMBER_OF_CLIENTS) { webSocketServer.close(); _this.assertEquals(false, webSocketServer.listening, "listening should be false"); - _this.ready(); + _this.done(); } }); newClient.send(JSON.stringify({message: TEST_MESSAGE, client: connectedClients})); diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 654b4746a8..0245da538d 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -39,7 +39,6 @@ void WebSocketClass::initialize() { QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { QString url; - QScriptValue callee = context->callee(); if (context->argumentCount() == 1) { url = context->argument(0).toString(); } From e581a57320495e3a5fc6b04ab5671428eafda9cc Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 21 Aug 2015 19:13:57 +0200 Subject: [PATCH 115/170] WebSocket constructor could allow more then one parameter now, only the first one is used though. --- libraries/script-engine/src/WebSocketClass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 0245da538d..27cb436998 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -39,7 +39,7 @@ void WebSocketClass::initialize() { QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { QString url; - if (context->argumentCount() == 1) { + if (context->argumentCount() > 0) { url = context->argument(0).toString(); } return engine->newQObject(new WebSocketClass(engine, url)); From b1db7e5ed25b100ed0b812c61cad787865ef9bec Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Aug 2015 10:28:47 -0700 Subject: [PATCH 116/170] Make WebWindow hyperlinks with target="_blank" open in user's browser --- interface/src/ui/DataWebPage.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index ab27509d28..69c9954245 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -32,12 +32,20 @@ void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumbe } bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) { + // Handle hifi:// links and links to files with particular extensions QString urlString = request.url().toString(); if (Application::getInstance()->canAcceptURL(urlString)) { if (Application::getInstance()->acceptURL(urlString)) { return false; // we handled it, so QWebPage doesn't need to handle it } } + + // Make hyperlinks with target="_blank" open in user's Web browser + if (type == QWebPage::NavigationTypeLinkClicked && frame == nullptr) { + Application::getInstance()->openUrl(request.url()); + return false; // We handled it. + } + return true; } From c7346209fb40aba7902d76c73e4bc503e47aa69b Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 21 Aug 2015 19:44:43 +0200 Subject: [PATCH 117/170] fix memory leak --- libraries/script-engine/src/WebSocketClass.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 27cb436998..52059d1290 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -34,7 +34,7 @@ void WebSocketClass::initialize() { connect(_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose); connect(_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage); connect(_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen); - _binaryType = "blob"; + _binaryType = QStringLiteral("blob"); } QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { @@ -46,7 +46,7 @@ QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* } WebSocketClass::~WebSocketClass() { - + _webSocket->deleteLater(); } void WebSocketClass::send(QScriptValue message) { From d3a68331339f89ed6c939a6b0aec49204b429cae Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 21 Aug 2015 20:20:50 +0200 Subject: [PATCH 118/170] delete WebSockets on ScriptEngine finish --- libraries/script-engine/src/WebSocketClass.cpp | 10 ++++++---- libraries/script-engine/src/WebSocketServerClass.cpp | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 52059d1290..7c7670fcce 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -42,7 +42,9 @@ QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* if (context->argumentCount() > 0) { url = context->argument(0).toString(); } - return engine->newQObject(new WebSocketClass(engine, url)); + auto webSocketClass = new WebSocketClass(engine, url); + connect(static_cast(engine), &ScriptEngine::finished, webSocketClass, &QObject::deleteLater); + return engine->newQObject(webSocketClass); } WebSocketClass::~WebSocketClass() { @@ -54,7 +56,7 @@ void WebSocketClass::send(QScriptValue message) { } void WebSocketClass::close() { - this->close(QWebSocketProtocol::CloseCode::CloseCodeNormal); + this->close(QWebSocketProtocol::CloseCodeNormal); } void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode) { @@ -67,7 +69,7 @@ void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reas void WebSocketClass::handleOnClose() { bool hasError = false; - if (_webSocket->error() != QAbstractSocket::SocketError::UnknownSocketError) { + if (_webSocket->error() != QAbstractSocket::UnknownSocketError) { hasError = true; if (_onErrorEvent.isFunction()) { _onErrorEvent.call(); @@ -76,7 +78,7 @@ void WebSocketClass::handleOnClose() { if (_onCloseEvent.isFunction()) { QScriptValueList args; QScriptValue arg = _engine->newObject(); - arg.setProperty("code", hasError ? QWebSocketProtocol::CloseCode::CloseCodeAbnormalDisconnection : _webSocket->closeCode()); + arg.setProperty("code", hasError ? QWebSocketProtocol::CloseCodeAbnormalDisconnection : _webSocket->closeCode()); arg.setProperty("reason", _webSocket->closeReason()); arg.setProperty("wasClean", !hasError); args << arg; diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp index dbe92510e4..045b39d800 100644 --- a/libraries/script-engine/src/WebSocketServerClass.cpp +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -49,6 +49,7 @@ WebSocketServerClass::~WebSocketServerClass() { if (_webSocketServer.isListening()) { close(); } + _clients.empty(); } void WebSocketServerClass::onNewConnection() { From 6ee22055fa6b5c3e1b40e4aee451522a7334b9a1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Aug 2015 12:48:08 -0700 Subject: [PATCH 119/170] More temporary logging to track down cause of assert --- interface/src/avatar/Avatar.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 19f84018f8..6d6120f3ba 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -748,11 +748,12 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa qDebug() << "ASSERT because isinf(scale)"; } qDebug() << "textPosition =" << textPosition; + qDebug() << "projMat =" << projMat; + qDebug() << "viewMat =" << viewMat; + qDebug() << "viewProj =" << viewProj; qDebug() << "windowSizeY =" << windowSizeY; - qDebug() << "p1.y =" << p1.y; - qDebug() << "p1.w =" << p1.w; - qDebug() << "p0.y =" << p0.y; - qDebug() << "p0.w =" << p0.w; + qDebug() << "p1 =" << p1; + qDebug() << "p0 =" << p0; qDebug() << "qApp->getDevicePixelRatio() =" << qApp->getDevicePixelRatio(); qDebug() << "fontSize =" << fontSize; qDebug() << "pixelHeight =" << pixelHeight; From d89ecc1e420cd01b0bf501bbb38d22b6b0686f3c Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 21 Aug 2015 22:43:49 +0200 Subject: [PATCH 120/170] Move WebSocketServer to be accessable from Assignment-Client:Agent only. --- assignment-client/src/Agent.cpp | 4 + .../utilities/diagnostics/testWebSocket.js | 73 ++++++++++--------- libraries/script-engine/src/ScriptEngine.cpp | 4 - 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d656464c10..4c3958c878 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -24,6 +24,7 @@ #include #include +#include #include // TODO: consider moving to scriptengine.h #include "avatars/ScriptableAvatar.h" @@ -184,6 +185,9 @@ void Agent::run() { _scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get().data()); + QScriptValue webSocketServerConstructorValue = _scriptEngine.newFunction(WebSocketServerClass::constructor); + _scriptEngine.globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); + auto entityScriptingInterface = DependencyManager::get(); _scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer); diff --git a/examples/utilities/diagnostics/testWebSocket.js b/examples/utilities/diagnostics/testWebSocket.js index af915c3790..4cf6c3682b 100644 --- a/examples/utilities/diagnostics/testWebSocket.js +++ b/examples/utilities/diagnostics/testWebSocket.js @@ -61,40 +61,43 @@ unitTests.addTest("Test WebSocket invalid URL", function(finished) { this.assertEquals(WEBSOCKET_INVALID_URL, webSocket.url, "url should be '" + WEBSOCKET_INVALID_URL + "'"); }, UNITTEST_TIMEOUT); - -unitTests.addTest("Test WebSocketServer with three clients", function(finished) { - var _this = this; - const NUMBER_OF_CLIENTS = 3; - var connectedClients = 0; - var respondedClients = 0; - var webSocketServer = new WebSocketServer(); - _this.assertEquals(true, webSocketServer.listening, "listening should be true"); - webSocketServer.newConnection.connect(this.registerCallbackFunction(function(newClient) { - connectedClients++; - newClient.onmessage = _this.registerCallbackFunction(function(event) { - var data = JSON.parse(event.data); - _this.assertEquals(TEST_MESSAGE, data.message, "data.message should be '" + TEST_MESSAGE + "'"); - respondedClients++; - if (respondedClients === NUMBER_OF_CLIENTS) { - webSocketServer.close(); - _this.assertEquals(false, webSocketServer.listening, "listening should be false"); - _this.done(); - } - }); - newClient.send(JSON.stringify({message: TEST_MESSAGE, client: connectedClients})); - })); - var newSocket1 = new WebSocket(webSocketServer.url); - newSocket1.onmessage = this.registerCallbackFunction(function(event) { - newSocket1.send(event.data); - }); - var newSocket2 = new WebSocket(webSocketServer.url); - newSocket2.onmessage = this.registerCallbackFunction(function(event) { - newSocket2.send(event.data); - }); - var newSocket3 = new WebSocket(webSocketServer.url); - newSocket3.onmessage = this.registerCallbackFunction(function(event) { - newSocket3.send(event.data); - }); -}, UNITTEST_TIMEOUT); +if (this.WebSocketServer === undefined) { + print("Skipping WebSocketServer tests."); +} else { + unitTests.addTest("Test WebSocketServer with three clients", function(finished) { + var _this = this; + const NUMBER_OF_CLIENTS = 3; + var connectedClients = 0; + var respondedClients = 0; + var webSocketServer = new WebSocketServer(); + _this.assertEquals(true, webSocketServer.listening, "listening should be true"); + webSocketServer.newConnection.connect(this.registerCallbackFunction(function(newClient) { + connectedClients++; + newClient.onmessage = _this.registerCallbackFunction(function(event) { + var data = JSON.parse(event.data); + _this.assertEquals(TEST_MESSAGE, data.message, "data.message should be '" + TEST_MESSAGE + "'"); + respondedClients++; + if (respondedClients === NUMBER_OF_CLIENTS) { + webSocketServer.close(); + _this.assertEquals(false, webSocketServer.listening, "listening should be false"); + _this.done(); + } + }); + newClient.send(JSON.stringify({message: TEST_MESSAGE, client: connectedClients})); + })); + var newSocket1 = new WebSocket(webSocketServer.url); + newSocket1.onmessage = this.registerCallbackFunction(function(event) { + newSocket1.send(event.data); + }); + var newSocket2 = new WebSocket(webSocketServer.url); + newSocket2.onmessage = this.registerCallbackFunction(function(event) { + newSocket2.send(event.data); + }); + var newSocket3 = new WebSocket(webSocketServer.url); + newSocket3.onmessage = this.registerCallbackFunction(function(event) { + newSocket3.send(event.data); + }); + }, UNITTEST_TIMEOUT); +} unitTests.run(); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c629529382..32a2a5b6f0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -39,7 +39,6 @@ #include "TypedArrays.h" #include "XMLHttpRequestClass.h" #include "WebSocketClass.h" -#include "WebSocketServerClass.h" #include "SceneScriptingInterface.h" @@ -348,9 +347,6 @@ void ScriptEngine::init() { QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor); globalObject().setProperty("WebSocket", webSocketConstructorValue); - QScriptValue webSocketServerConstructorValue = newFunction(WebSocketServerClass::constructor); - globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); - QScriptValue printConstructorValue = newFunction(debugPrint); globalObject().setProperty("print", printConstructorValue); From 176d7372adf72fbc3c7d61af52534ee52e9bc297 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 21 Aug 2015 23:03:26 +0200 Subject: [PATCH 121/170] add QWebSockets to assignment-client project (cmake) --- assignment-client/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 315eeb6b83..dc20763953 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME assignment-client) -setup_hifi_project(Core Gui Network Script Widgets) +setup_hifi_project(Core Gui Network Script Widgets WebSockets) add_dependency_external_projects(glm) find_package(GLM REQUIRED) From 220ad189a04128e1926182b6170e1f46d1cbd6bf Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 21 Aug 2015 14:16:34 -0700 Subject: [PATCH 122/170] force the HTTPManager to listen on IPv4 --- .../embedded-webserver/src/HTTPManager.cpp | 27 ++++++------------- .../embedded-webserver/src/HTTPManager.h | 7 ----- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 72436fc55e..946da00a34 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -29,10 +29,14 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH _requestHandler(requestHandler), _port(port) { - bindSocket(); - _isListeningTimer = new QTimer(this); - connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening); - _isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS); + qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); + + if (listen(QHostAddress::AnyIPv4, _port)) { + qCDebug(embeddedwebserver) << "TCP socket is listening on" << serverAddress() << "and port" << serverPort(); + } else { + qCritical() << "Failed to open HTTP server socket:" << errorString() << " - forcing exit."; + QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); + } } void HTTPManager::incomingConnection(qintptr socketDescriptor) { @@ -162,18 +166,3 @@ bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, con return _requestHandler && _requestHandler->handleHTTPRequest(connection, url); } -void HTTPManager::isTcpServerListening() { - if (!isListening()) { - qCWarning(embeddedwebserver) << "Socket on port " << QString::number(_port) << " is no longer listening"; - bindSocket(); - } -} - -bool HTTPManager::bindSocket() { - qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); - if (!listen(QHostAddress::Any, _port)) { - qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; - QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); - } - return true; -} \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 6375b10205..8e1780a631 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -37,12 +37,6 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); -private slots: - void isTcpServerListening(); - -private: - bool bindSocket(); - protected: /// Accepts all pending connections virtual void incomingConnection(qintptr socketDescriptor); @@ -50,7 +44,6 @@ protected: QString _documentRoot; HTTPRequestHandler* _requestHandler; - QTimer* _isListeningTimer; const quint16 _port; }; From ad0df3a47c8b1801be37c1aa12f768459694289a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 21 Aug 2015 14:21:49 -0700 Subject: [PATCH 123/170] Make sixense work in dev builds (if you put the library in the documented place). --- libraries/input-plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index 4428327deb..5d6dae7ad1 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -6,7 +6,7 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) string(TOLOWER ${EXTERNAL} ${EXTERNAL}_LOWERCASE) - set(${${EXTERNAL}_UPPERCASE}_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/${${EXTERNAL}_LOWERCASE}") + set(${${EXTERNAL}_UPPERCASE}_ROOT_DIR "${CMAKE_SOURCE_DIR}/interface/external/${${EXTERNAL}_LOWERCASE}") endif () endforeach() From 34b3fb1c0c7e2230b55f8f9afee889ecf9be5512 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 21 Aug 2015 14:27:17 -0700 Subject: [PATCH 124/170] Revert "force the HTTPManager to listen on IPv4" This reverts commit 220ad189a04128e1926182b6170e1f46d1cbd6bf. --- .../embedded-webserver/src/HTTPManager.cpp | 27 +++++++++++++------ .../embedded-webserver/src/HTTPManager.h | 7 +++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 946da00a34..72436fc55e 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -29,14 +29,10 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH _requestHandler(requestHandler), _port(port) { - qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); - - if (listen(QHostAddress::AnyIPv4, _port)) { - qCDebug(embeddedwebserver) << "TCP socket is listening on" << serverAddress() << "and port" << serverPort(); - } else { - qCritical() << "Failed to open HTTP server socket:" << errorString() << " - forcing exit."; - QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); - } + bindSocket(); + _isListeningTimer = new QTimer(this); + connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening); + _isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS); } void HTTPManager::incomingConnection(qintptr socketDescriptor) { @@ -166,3 +162,18 @@ bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, con return _requestHandler && _requestHandler->handleHTTPRequest(connection, url); } +void HTTPManager::isTcpServerListening() { + if (!isListening()) { + qCWarning(embeddedwebserver) << "Socket on port " << QString::number(_port) << " is no longer listening"; + bindSocket(); + } +} + +bool HTTPManager::bindSocket() { + qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); + if (!listen(QHostAddress::Any, _port)) { + qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; + QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); + } + return true; +} \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 8e1780a631..6375b10205 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -37,6 +37,12 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); +private slots: + void isTcpServerListening(); + +private: + bool bindSocket(); + protected: /// Accepts all pending connections virtual void incomingConnection(qintptr socketDescriptor); @@ -44,6 +50,7 @@ protected: QString _documentRoot; HTTPRequestHandler* _requestHandler; + QTimer* _isListeningTimer; const quint16 _port; }; From 3e4a05a57bf7284537065288605f8ad597f6b226 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 21 Aug 2015 14:30:41 -0700 Subject: [PATCH 125/170] leave the listen check but queue Application quit --- .../embedded-webserver/src/HTTPManager.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 72436fc55e..4498d619cd 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -20,7 +20,6 @@ #include "EmbeddedWebserverLogging.h" #include "HTTPManager.h" -const int SOCKET_ERROR_EXIT_CODE = 2; const int SOCKET_CHECK_INTERVAL_IN_MS = 30000; HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : @@ -30,6 +29,7 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH _port(port) { bindSocket(); + _isListeningTimer = new QTimer(this); connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening); _isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS); @@ -171,9 +171,16 @@ void HTTPManager::isTcpServerListening() { bool HTTPManager::bindSocket() { qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); - if (!listen(QHostAddress::Any, _port)) { + + if (listen(QHostAddress::AnyIPv4, _port)) { + qCDebug(embeddedwebserver) << "TCP socket is listening on" << serverAddress() << "and port" << serverPort(); + + return true; + } else { qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; - QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); + QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); + + return false; } - return true; -} \ No newline at end of file + +} From 0fbbfb77d1d66df91e5130613e5fec1b6606129b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 21 Aug 2015 14:42:03 -0700 Subject: [PATCH 126/170] exit with a code to indicate error --- libraries/embedded-webserver/src/HTTPManager.cpp | 10 +++++++--- libraries/embedded-webserver/src/HTTPManager.h | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 4498d619cd..525caf8f3c 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -20,6 +20,7 @@ #include "EmbeddedWebserverLogging.h" #include "HTTPManager.h" +const int SOCKET_ERROR_EXIT_CODE = 2; const int SOCKET_CHECK_INTERVAL_IN_MS = 30000; HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : @@ -172,15 +173,18 @@ void HTTPManager::isTcpServerListening() { bool HTTPManager::bindSocket() { qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); - if (listen(QHostAddress::AnyIPv4, _port)) { + if (false) { qCDebug(embeddedwebserver) << "TCP socket is listening on" << serverAddress() << "and port" << serverPort(); return true; } else { qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; - QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "queuedExit", Qt::QueuedConnection); return false; } - +} + +void HTTPManager::queuedExit() { + QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); } diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 6375b10205..03498fbe8d 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -39,6 +39,7 @@ public: private slots: void isTcpServerListening(); + void queuedExit(); private: bool bindSocket(); From 4b95d54955f0264b826cf6a3ca4cc9c7dc29ad84 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 21 Aug 2015 14:55:24 -0700 Subject: [PATCH 127/170] remove the explicit failure test --- libraries/embedded-webserver/src/HTTPManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 525caf8f3c..1f1dfa1735 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -173,7 +173,7 @@ void HTTPManager::isTcpServerListening() { bool HTTPManager::bindSocket() { qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); - if (false) { + if (listen(QHostAddress::AnyIPv4, _port)) { qCDebug(embeddedwebserver) << "TCP socket is listening on" << serverAddress() << "and port" << serverPort(); return true; From b579b996c34a0bd67c8517ed97c8d9e9aa77eae7 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 21 Aug 2015 15:07:39 -0700 Subject: [PATCH 128/170] There was a hidden (and inappropriate) dependency on face model loading. --- interface/src/avatar/Avatar.cpp | 9 ++++----- interface/src/avatar/MyAvatar.cpp | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 19f84018f8..59ae933030 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -469,8 +469,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { * (1.0f - ((float)(now - getHead()->getLookingAtMeStarted())) / (LOOKING_AT_ME_DURATION * (float)USECS_PER_SECOND)); if (alpha > 0.0f) { - QSharedPointer geometry = getHead()->getFaceModel().getGeometry(); - if (geometry) { + QSharedPointer geometry = _skeletonModel.getGeometry(); + if (geometry && geometry->isLoaded()) { const float DEFAULT_EYE_DIAMETER = 0.048f; // Typical human eye const float RADIUS_INCREMENT = 0.005f; Transform transform; @@ -597,13 +597,12 @@ void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, floa if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { // render the billboard until both models are loaded renderBillboard(renderArgs); - return; + } else { + getHead()->render(renderArgs, 1.0f, renderFrustum); } getHand()->render(renderArgs, false); } - - getHead()->render(renderArgs, 1.0f, renderFrustum); getHead()->renderLookAts(renderArgs); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index eada41eb29..1fec536082 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1141,7 +1141,7 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, const g void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel) { - if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { + if (!_skeletonModel.isRenderable()) { return; // wait until all models are loaded } From b7009b46318404484e6b8479a8167ee84f90eadf Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 21 Aug 2015 16:09:06 -0700 Subject: [PATCH 129/170] Fix for animation resources The problem was that the invokeMethod between the AnimationReader thread and the main thread was failing, because FBXGeometry* wasn't a registered meta type. So, I ended up normalizing the AnimationReader class to be more like GeometryReader, in that it uses singles and slots to communicate success and failure, rather then invokeMethod. --- examples/libraries/progressDialog.js | 4 +- libraries/animation/src/AnimationCache.cpp | 71 +++++++++++++-------- libraries/animation/src/AnimationCache.h | 24 ++++++- libraries/animation/src/AnimationHandle.cpp | 1 + libraries/fbx/src/FBXReader.cpp | 8 ++- libraries/render-utils/src/GeometryCache.h | 9 --- 6 files changed, 77 insertions(+), 40 deletions(-) diff --git a/examples/libraries/progressDialog.js b/examples/libraries/progressDialog.js index 7d3b1f88e2..4cb2644004 100644 --- a/examples/libraries/progressDialog.js +++ b/examples/libraries/progressDialog.js @@ -8,6 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var toolIconUrl = HIFI_PUBLIC_BUCKET + "images/tools/"; progressDialog = (function () { var that = {}, @@ -142,4 +144,4 @@ progressDialog = (function () { that.cleanup = cleanup; return that; -}()); \ No newline at end of file +}()); diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 7f3f393a8b..c9514782ee 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -39,35 +39,44 @@ QSharedPointer AnimationCache::createResource(const QUrl& url, const Q return QSharedPointer(new Animation(url), &Resource::allReferencesCleared); } -Animation::Animation(const QUrl& url) : Resource(url) {} - -class AnimationReader : public QRunnable { -public: - - AnimationReader(const QWeakPointer& animation, QNetworkReply* reply); - - virtual void run(); - -private: - - QWeakPointer _animation; - QNetworkReply* _reply; -}; - -AnimationReader::AnimationReader(const QWeakPointer& animation, QNetworkReply* reply) : - _animation(animation), +AnimationReader::AnimationReader(const QUrl& url, QNetworkReply* reply) : + _url(url), _reply(reply) { } void AnimationReader::run() { - QSharedPointer animation = _animation.toStrongRef(); - if (!animation.isNull()) { - QMetaObject::invokeMethod(animation.data(), "setGeometry", - Q_ARG(FBXGeometry*, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString()))); + try { + if (!_reply) { + throw QString("Reply is NULL ?!"); + } + QString urlname = _url.path().toLower(); + bool urlValid = true; + urlValid &= !urlname.isEmpty(); + urlValid &= !_url.path().isEmpty(); + urlValid &= _url.path().toLower().endsWith(".fbx"); + + if (urlValid) { + // Let's read the binaries from the network + FBXGeometry* fbxgeo = nullptr; + if (_url.path().toLower().endsWith(".fbx")) { + fbxgeo = readFBX(_reply, QVariantHash(), _url.path()); + } else { + QString errorStr("usupported format"); + emit onError(299, errorStr); + } + emit onSuccess(fbxgeo); + } else { + throw QString("url is invalid"); + } + + } catch (const QString& error) { + emit onError(299, error); } _reply->deleteLater(); } +Animation::Animation(const QUrl& url) : Resource(url) {} + bool Animation::isLoaded() const { return _loaded && _geometry; } @@ -100,17 +109,27 @@ const QVector& Animation::getFramesReference() const { return _geometry->animationFrames; } -void Animation::setGeometry(FBXGeometry* geometry) { +void Animation::downloadFinished(QNetworkReply* reply) { + // parse the animation/fbx file on a background thread. + AnimationReader* animationReader = new AnimationReader(reply->url(), reply); + connect(animationReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(animationParseSuccess(FBXGeometry*))); + connect(animationReader, SIGNAL(onError(int, QString)), SLOT(animationParseError(int, QString))); + QThreadPool::globalInstance()->start(animationReader); +} + +void Animation::animationParseSuccess(FBXGeometry* geometry) { + + qCDebug(animation) << "Animation parse success" << _url.toDisplayString(); + _geometry.reset(geometry); finishedLoading(true); } -void Animation::downloadFinished(QNetworkReply* reply) { - // send the reader off to the thread pool - QThreadPool::globalInstance()->start(new AnimationReader(_self, reply)); +void Animation::animationParseError(int error, QString str) { + qCCritical(animation) << "Animation failure parsing " << _url.toDisplayString() << "code =" << error << str; + emit failed(QNetworkReply::UnknownContentError); } - AnimationDetails::AnimationDetails() : role(), url(), fps(0.0f), priority(0.0f), loop(false), hold(false), startAutomatically(false), firstFrame(0.0f), lastFrame(0.0f), running(false), frameIndex(0.0f) diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 3ff5957fa2..af07eda9a4 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -12,6 +12,7 @@ #ifndef hifi_AnimationCache_h #define hifi_AnimationCache_h +#include #include #include @@ -64,16 +65,33 @@ public: const QVector& getFramesReference() const; protected: - - Q_INVOKABLE void setGeometry(FBXGeometry* geometry); - virtual void downloadFinished(QNetworkReply* reply); +protected slots: + void animationParseSuccess(FBXGeometry* geometry); + void animationParseError(int error, QString str); + private: std::unique_ptr _geometry; }; +/// Reads geometry in a worker thread. +class AnimationReader : public QObject, public QRunnable { + Q_OBJECT + +public: + AnimationReader(const QUrl& url, QNetworkReply* reply); + virtual void run(); + +signals: + void onSuccess(FBXGeometry* geometry); + void onError(int error, QString str); + +private: + QUrl _url; + QNetworkReply* _reply; +}; class AnimationDetails { public: diff --git a/libraries/animation/src/AnimationHandle.cpp b/libraries/animation/src/AnimationHandle.cpp index ebf0e29b97..1f4c886a06 100644 --- a/libraries/animation/src/AnimationHandle.cpp +++ b/libraries/animation/src/AnimationHandle.cpp @@ -15,6 +15,7 @@ void AnimationHandle::setURL(const QUrl& url) { if (_url != url) { _animation = DependencyManager::get()->getAnimation(_url = url); + _animation->ensureLoading(); QObject::connect(_animation.data(), &Resource::onRefresh, this, &AnimationHandle::clearJoints); _jointMappings.clear(); } diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index f0d13f8792..320b93ff06 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -182,7 +182,7 @@ QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { static int fbxGeometryMetaTypeId = qRegisterMetaType(); static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); -static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); +static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType>(); template int streamSize() { return sizeof(T); @@ -1858,12 +1858,18 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping foreach (const FBXNode& subobject, object.children) { if (subobject.name == "RelativeFilename") { QByteArray filename = subobject.properties.at(0).toByteArray(); + qCDebug(modelformat) << "AJT: RelativeFilename, filename =" << filename; filename = fileOnUrl(filename, url); + qCDebug(modelformat) << "AJT: url = " << url; + qCDebug(modelformat) << "AJT: filename2 = " << filename; + textureFilenames.insert(getID(object.properties), filename); } else if (subobject.name == "TextureName") { // trim the name from the timestamp QString name = QString(subobject.properties.at(0).toByteArray()); + qCDebug(modelformat) << "AJT: TextureName, name =" << name; name = name.left(name.indexOf('[')); + qCDebug(modelformat) << "AJT: name2 =" << name; textureNames.insert(getID(object.properties), name); } else if (subobject.name == "Texture_Alpha_Source") { tex.assign(tex.alphaSource, subobject.properties.at(0).value()); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 774df1561c..0b692d81f7 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -21,8 +21,6 @@ #include "FBXReader.h" #include "OBJReader.h" -#include - #include #include @@ -383,20 +381,13 @@ protected: /// Reads geometry in a worker thread. class GeometryReader : public QObject, public QRunnable { Q_OBJECT - public: - GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping); - virtual void run(); - signals: void onSuccess(FBXGeometry* geometry); void onError(int error, QString str); - private: - - QWeakPointer _geometry; QUrl _url; QNetworkReply* _reply; QVariantHash _mapping; From 518cf3be1504234eb0dc22906876e292b2186f57 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 21 Aug 2015 16:28:38 -0700 Subject: [PATCH 130/170] Improve the light attenuation formulae --- libraries/model/src/model/Light.cpp | 11 +++++++++-- libraries/model/src/model/Light.h | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/libraries/model/src/model/Light.cpp b/libraries/model/src/model/Light.cpp index 525f68e640..823be727a4 100755 --- a/libraries/model/src/model/Light.cpp +++ b/libraries/model/src/model/Light.cpp @@ -58,10 +58,12 @@ const Vec3& Light::getDirection() const { void Light::setColor(const Color& color) { editSchema()._color = color; + updateLightRadius(); } void Light::setIntensity(float intensity) { editSchema()._intensity = intensity; + updateLightRadius(); } void Light::setAmbientIntensity(float intensity) { @@ -72,9 +74,14 @@ void Light::setMaximumRadius(float radius) { if (radius <= 0.f) { radius = 1.0f; } + editSchema()._attenuation.w = radius; + updateLightRadius(); +} + +void Light::updateLightRadius() { float CutOffIntensityRatio = 0.05f; - float surfaceRadius = radius / (sqrtf(1.0f / CutOffIntensityRatio) - 1.0f); - editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, radius); + float surfaceRadius = getMaximumRadius() / (sqrtf((getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z)) / CutOffIntensityRatio) - 1.0f); + editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, getMaximumRadius()); } #include diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index 1ed07a942c..3fbaba75bf 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -127,6 +127,8 @@ protected: const Schema& getSchema() const { return _schemaBuffer.get(); } Schema& editSchema() { return _schemaBuffer.edit(); } + + void updateLightRadius(); }; typedef std::shared_ptr< Light > LightPointer; From f512205f6f22b26eb7a864e2eeb27c1fee4d7468 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 22 Aug 2015 01:30:07 +0200 Subject: [PATCH 131/170] add ability to include scripts from a relative path in assignment-client (for persistent scripts only) --- assignment-client/src/Agent.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4c3958c878..300976f81c 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -181,6 +181,10 @@ void Agent::run() { // register ourselves to the script engine _scriptEngine.registerGlobalObject("Agent", this); + if (!_payload.isEmpty()) { + _scriptEngine.setParentURL(_payload); + } + _scriptEngine.init(); // must be done before we set up the viewers _scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get().data()); From 9edba451eb989f68d9190887566dc610a0524027 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 22 Aug 2015 01:33:22 +0200 Subject: [PATCH 132/170] - remove webSockets from List after disconnect. - leave it up to the QtScript to delete the WebSocket / WebSocketServer objects --- libraries/script-engine/src/WebSocketClass.cpp | 6 ++---- libraries/script-engine/src/WebSocketClass.h | 2 ++ libraries/script-engine/src/WebSocketServerClass.cpp | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 7c7670fcce..8341d65d81 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -42,9 +42,7 @@ QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* if (context->argumentCount() > 0) { url = context->argument(0).toString(); } - auto webSocketClass = new WebSocketClass(engine, url); - connect(static_cast(engine), &ScriptEngine::finished, webSocketClass, &QObject::deleteLater); - return engine->newQObject(webSocketClass); + return engine->newQObject(new WebSocketClass(engine, url), QScriptEngine::ScriptOwnership); } WebSocketClass::~WebSocketClass() { @@ -111,7 +109,7 @@ void qWSCloseCodeFromScriptValue(const QScriptValue &object, QWebSocketProtocol: } QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in) { - return engine->newQObject(in); + return engine->newQObject(in, QScriptEngine::ScriptOwnership); } void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out) { diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h index e053257e3d..b87d578843 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -50,6 +50,8 @@ public: CLOSED }; + QWebSocket* getWebSocket() { return _webSocket; } + int getConnecting() const { return CONNECTING; }; int getOpen() const { return OPEN; }; int getClosing() const { return CLOSING; }; diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp index 045b39d800..3b3a02a7c9 100644 --- a/libraries/script-engine/src/WebSocketServerClass.cpp +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -40,9 +40,7 @@ QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptE serverName = serverNameOption.toString(); } } - auto webSocketServerClass = new WebSocketServerClass(engine, serverName, port); - connect(static_cast(engine), &ScriptEngine::finished, webSocketServerClass, &QObject::deleteLater); - return engine->newQObject(webSocketServerClass); + return engine->newQObject(new WebSocketServerClass(engine, serverName, port), QScriptEngine::ScriptOwnership); } WebSocketServerClass::~WebSocketServerClass() { @@ -55,6 +53,9 @@ WebSocketServerClass::~WebSocketServerClass() { void WebSocketServerClass::onNewConnection() { WebSocketClass* newClient = new WebSocketClass(_engine, _webSocketServer.nextPendingConnection()); _clients << newClient; + connect(newClient->getWebSocket(), &QWebSocket::disconnected, [newClient, this]() { + _clients.removeOne(newClient); + }); emit newConnection(newClient); } From 74fd640f160644a031731116d3f3cd60fcd597de Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 21 Aug 2015 16:49:07 -0700 Subject: [PATCH 133/170] Restore the ability to change avatars from the edit.js marketplace button. --- interface/src/Application.cpp | 1 + interface/src/ui/PreferencesDialog.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 50d60b4cb3..3fd2899360 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4125,6 +4125,7 @@ bool Application::askToSetAvatarUrl(const QString& url) { msgBox.exec(); if (msgBox.clickedButton() == bodyAndHeadButton) { + _myAvatar->useFullAvatarURL(url, modelName); emit fullAvatarURLChanged(url, modelName); } else { qCDebug(interfaceapp) << "Declined to use the avatar: " << url; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index a6340d3955..4dfa6af96f 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -64,7 +64,6 @@ void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QStr ui.appearanceDescription->setText(newValue); const QString APPEARANCE_LABEL_TEXT("Appearance: "); ui.appearanceLabel->setText(APPEARANCE_LABEL_TEXT + modelName); - DependencyManager::get()->getMyAvatar()->useFullAvatarURL(newValue, modelName); } void PreferencesDialog::accept() { @@ -78,7 +77,9 @@ void PreferencesDialog::accept() { } void PreferencesDialog::restoreLastGoodAvatar() { - fullAvatarURLChanged(_lastGoodAvatarURL.toString(), _lastGoodAvatarName); + const QString& url = _lastGoodAvatarURL.toString(); + fullAvatarURLChanged(url, _lastGoodAvatarName); + DependencyManager::get()->getMyAvatar()->useFullAvatarURL(url, _lastGoodAvatarName); } void PreferencesDialog::reject() { From 5e31d423b9a563c559c32c15423f7165ae83c4e9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 21 Aug 2015 16:55:08 -0700 Subject: [PATCH 134/170] Removed some debugging statements --- libraries/fbx/src/FBXReader.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 320b93ff06..f0d13f8792 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -182,7 +182,7 @@ QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { static int fbxGeometryMetaTypeId = qRegisterMetaType(); static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); -static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType>(); +static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); template int streamSize() { return sizeof(T); @@ -1858,18 +1858,12 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping foreach (const FBXNode& subobject, object.children) { if (subobject.name == "RelativeFilename") { QByteArray filename = subobject.properties.at(0).toByteArray(); - qCDebug(modelformat) << "AJT: RelativeFilename, filename =" << filename; filename = fileOnUrl(filename, url); - qCDebug(modelformat) << "AJT: url = " << url; - qCDebug(modelformat) << "AJT: filename2 = " << filename; - textureFilenames.insert(getID(object.properties), filename); } else if (subobject.name == "TextureName") { // trim the name from the timestamp QString name = QString(subobject.properties.at(0).toByteArray()); - qCDebug(modelformat) << "AJT: TextureName, name =" << name; name = name.left(name.indexOf('[')); - qCDebug(modelformat) << "AJT: name2 =" << name; textureNames.insert(getID(object.properties), name); } else if (subobject.name == "Texture_Alpha_Source") { tex.assign(tex.alphaSource, subobject.properties.at(0).value()); From 14a76525b18e686d3c14c9c84f2bc0fa5c71df2a Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Fri, 21 Aug 2015 16:56:26 -0700 Subject: [PATCH 135/170] script dependency...? (GeometryCache -> AnimationCache -> networking, fbx, model, animation, script... >.>) --- tests/gpu-test/CMakeLists.txt | 2 +- tests/gpu-test/src/main.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index faedca537e..944c54a810 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -10,6 +10,6 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") #include_oglplus() # link in the shared libraries -link_hifi_libraries(render-utils gpu shared networking fbx model animation) +link_hifi_libraries(render-utils gpu shared networking fbx model animation script) copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 7cc6adbd50..1a95d08902 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -243,7 +243,6 @@ void renderCube(gpu::Batch & batch, const BasicModel & cube) { batch.draw(gpu::TRIANGLES, 24); } - gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(vertexShaderSrc)); auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSrc)); From 0e255f4f3ee785b6c35799423e0dad447e2291a1 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 22 Aug 2015 01:59:44 +0200 Subject: [PATCH 136/170] properly exposed WebSocketClass::ReadyState to the ScriptEngine --- libraries/script-engine/src/ScriptEngine.cpp | 1 + .../script-engine/src/WebSocketClass.cpp | 8 ++++++ libraries/script-engine/src/WebSocketClass.h | 25 +++++++++++-------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 32a2a5b6f0..b42daa710a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -359,6 +359,7 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue); qScriptRegisterMetaType(this, webSocketToScriptValue, webSocketFromScriptValue); qScriptRegisterMetaType(this, qWSCloseCodeToScriptValue, qWSCloseCodeFromScriptValue); + qScriptRegisterMetaType(this, wscReadyStateToScriptValue, wscReadyStateFromScriptValue); registerGlobalObject("Script", this); registerGlobalObject("Audio", &AudioScriptingInterface::getInstance()); diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 8341d65d81..fd923965cf 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -115,3 +115,11 @@ QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out) { out = qobject_cast(object.toQObject()); } + +QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState) { + return readyState; +} + +void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState) { + readyState = (WebSocketClass::ReadyState)object.toUInt16(); +} diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h index b87d578843..9f14b44ba0 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -28,13 +28,13 @@ class WebSocketClass : public QObject { Q_PROPERTY(QScriptValue onopen READ getOnOpen WRITE setOnOpen) Q_PROPERTY(QString protocol READ getProtocol) - Q_PROPERTY(uint readyState READ getReadyState) + Q_PROPERTY(WebSocketClass::ReadyState readyState READ getReadyState) Q_PROPERTY(QString url READ getURL) - Q_PROPERTY(int CONNECTING READ getConnecting CONSTANT) - Q_PROPERTY(int OPEN READ getOpen CONSTANT) - Q_PROPERTY(int CLOSING READ getClosing CONSTANT) - Q_PROPERTY(int CLOSED READ getClosed CONSTANT) + Q_PROPERTY(WebSocketClass::ReadyState CONNECTING READ getConnecting CONSTANT) + Q_PROPERTY(WebSocketClass::ReadyState OPEN READ getOpen CONSTANT) + Q_PROPERTY(WebSocketClass::ReadyState CLOSING READ getClosing CONSTANT) + Q_PROPERTY(WebSocketClass::ReadyState CLOSED READ getClosed CONSTANT) public: WebSocketClass(QScriptEngine* engine, QString url); @@ -52,10 +52,10 @@ public: QWebSocket* getWebSocket() { return _webSocket; } - int getConnecting() const { return CONNECTING; }; - int getOpen() const { return OPEN; }; - int getClosing() const { return CLOSING; }; - int getClosed() const { return CLOSED; }; + ReadyState getConnecting() const { return CONNECTING; }; + ReadyState getOpen() const { return OPEN; }; + ReadyState getClosing() const { return CLOSING; }; + ReadyState getClosed() const { return CLOSED; }; void setBinaryType(QString binaryType) { _binaryType = binaryType; } QString getBinaryType() { return _binaryType; } @@ -70,7 +70,7 @@ public: QString getURL() { return _webSocket->requestUrl().toDisplayString(); } - uint getReadyState() { + ReadyState getReadyState() { switch (_webSocket->state()) { case QAbstractSocket::SocketState::HostLookupState: case QAbstractSocket::SocketState::ConnectingState: @@ -125,10 +125,15 @@ private slots: }; Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode); +Q_DECLARE_METATYPE(WebSocketClass::ReadyState); + QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode& closeCode); void qWSCloseCodeFromScriptValue(const QScriptValue& object, QWebSocketProtocol::CloseCode& closeCode); QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in); void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out); +QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState); +void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState); + #endif // hifi_WebSocketClass_h From d84cf1859c25e9e94d2613da9367f0d53ffd38a8 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 22 Aug 2015 02:18:21 +0200 Subject: [PATCH 137/170] hook up the WebSocket error event directly --- libraries/script-engine/src/WebSocketClass.cpp | 16 +++++++++------- libraries/script-engine/src/WebSocketClass.h | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index fd923965cf..c844dc3582 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -34,6 +34,8 @@ void WebSocketClass::initialize() { connect(_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose); connect(_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage); connect(_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen); + connect(_webSocket, static_cast(&QWebSocket::error), this, + &WebSocketClass::handleOnError); _binaryType = QStringLiteral("blob"); } @@ -66,13 +68,7 @@ void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reas } void WebSocketClass::handleOnClose() { - bool hasError = false; - if (_webSocket->error() != QAbstractSocket::UnknownSocketError) { - hasError = true; - if (_onErrorEvent.isFunction()) { - _onErrorEvent.call(); - } - } + bool hasError = (_webSocket->error() != QAbstractSocket::UnknownSocketError); if (_onCloseEvent.isFunction()) { QScriptValueList args; QScriptValue arg = _engine->newObject(); @@ -84,6 +80,12 @@ void WebSocketClass::handleOnClose() { } } +void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) { + if (_onErrorEvent.isFunction()) { + _onErrorEvent.call(); + } +} + void WebSocketClass::handleOnMessage(const QString& message) { if (_onMessageEvent.isFunction()) { QScriptValueList args; diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h index 9f14b44ba0..8ba8ecf362 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -119,6 +119,7 @@ private: private slots: void handleOnClose(); + void handleOnError(QAbstractSocket::SocketError error); void handleOnMessage(const QString& message); void handleOnOpen(); From e601484b17ff8219ab94ce79410a8cb6e7864c3a Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Fri, 21 Aug 2015 17:24:34 -0700 Subject: [PATCH 138/170] ... --- tests/gpu-test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 944c54a810..19a41df776 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -4,12 +4,12 @@ set(TARGET_NAME gpu-test) AUTOSCRIBE_SHADER_LIB(gpu model render gpu-test) # This is not a testcase -- just set it up as a regular hifi project -setup_hifi_project(Quick Gui OpenGL) +setup_hifi_project(Quick Gui OpenGL Script Widgets) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") #include_oglplus() # link in the shared libraries -link_hifi_libraries(render-utils gpu shared networking fbx model animation script) +link_hifi_libraries(render-utils gpu shared networking fbx model animation script-engine) copy_dlls_beside_windows_executable() \ No newline at end of file From 67e945b8847a833f8ad9ac0800984ecafc40ee98 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 22 Aug 2015 03:32:53 +0200 Subject: [PATCH 139/170] fix unit test --- examples/utilities/diagnostics/testWebSocket.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/utilities/diagnostics/testWebSocket.js b/examples/utilities/diagnostics/testWebSocket.js index 4cf6c3682b..c356fd99a8 100644 --- a/examples/utilities/diagnostics/testWebSocket.js +++ b/examples/utilities/diagnostics/testWebSocket.js @@ -51,11 +51,10 @@ unitTests.addTest("Test WebSocket invalid URL", function(finished) { var hadError = false; webSocket.onerror = this.registerCallbackFunction(function() { hadError = true; + _this.done(); }); webSocket.onclose = this.registerCallbackFunction(function(event) { - _this.assertEquals(true, hadError, "hadError should be true"); _this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED"); - _this.done(); }); this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING"); this.assertEquals(WEBSOCKET_INVALID_URL, webSocket.url, "url should be '" + WEBSOCKET_INVALID_URL + "'"); From 04e7084743a1c50fac9b1040c6b3878b91aa9601 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 21 Aug 2015 21:43:21 -0700 Subject: [PATCH 140/170] Resource fix for textures in FBX files. The baseTexturePath url for textures in FBXGeometry should default to the same base url as the fbx file itself. This error was introduced in my recent refactoring. Textures embedded in FBXGeometries should be un-affected by this change. --- libraries/render-utils/src/GeometryCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f48ceb9b62..c778376e92 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1723,7 +1723,7 @@ void GeometryReader::run() { NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : _url(url), _mapping(mapping), - _textureBaseUrl(textureBaseUrl) { + _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) { if (delayLoad) { _state = DelayState; From 80d3ae68c23c99fbd6b1922a8463343716040a93 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 21 Aug 2015 22:11:53 -0700 Subject: [PATCH 141/170] =?UTF-8?q?Removed=20redundant=20ends=20with=20?= =?UTF-8?q?=E2=80=98fbx=E2=80=99=20check.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libraries/animation/src/AnimationCache.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index c9514782ee..e7a4fb50a3 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -53,10 +53,9 @@ void AnimationReader::run() { bool urlValid = true; urlValid &= !urlname.isEmpty(); urlValid &= !_url.path().isEmpty(); - urlValid &= _url.path().toLower().endsWith(".fbx"); if (urlValid) { - // Let's read the binaries from the network + // Parse the FBX directly from the QNetworkReply FBXGeometry* fbxgeo = nullptr; if (_url.path().toLower().endsWith(".fbx")) { fbxgeo = readFBX(_reply, QVariantHash(), _url.path()); From d3135dcfa6167052e6a34520e0f5e64db7a4ba0f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 20 Aug 2015 17:04:53 -0700 Subject: [PATCH 142/170] Stereo rendering work --- interface/src/Application.cpp | 156 +++-------------- interface/src/Application.h | 26 +-- interface/src/Menu.cpp | 4 +- interface/src/PluginContainerProxy.cpp | 161 ++++++++++++++++++ interface/src/PluginContainerProxy.h | 30 ++++ .../Basic2DWindowOpenGLDisplayPlugin.cpp | 1 - .../src/display-plugins/DisplayPlugin.cpp | 8 +- .../src/display-plugins/DisplayPlugin.h | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 2 +- .../src/display-plugins/OpenGLDisplayPlugin.h | 1 + .../WindowOpenGLDisplayPlugin.cpp | 5 + .../WindowOpenGLDisplayPlugin.h | 1 + .../stereo/InterleavedStereoDisplayPlugin.cpp | 53 +++++- .../stereo/InterleavedStereoDisplayPlugin.h | 3 + .../stereo/SideBySideStereoDisplayPlugin.cpp | 23 +-- .../stereo/SideBySideStereoDisplayPlugin.h | 3 +- .../stereo/StereoDisplayPlugin.cpp | 44 ++++- .../stereo/StereoDisplayPlugin.h | 2 + libraries/plugins/src/plugins/Forward.h | 23 +++ libraries/plugins/src/plugins/Plugin.cpp | 7 + libraries/plugins/src/plugins/Plugin.h | 9 +- .../plugins/src/plugins/PluginContainer.h | 7 +- .../plugins/src/plugins/PluginManager.cpp | 8 +- libraries/plugins/src/plugins/PluginManager.h | 20 +-- 24 files changed, 397 insertions(+), 202 deletions(-) create mode 100644 interface/src/PluginContainerProxy.cpp create mode 100644 interface/src/PluginContainerProxy.h create mode 100644 libraries/plugins/src/plugins/Forward.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 50d60b4cb3..0f3282582f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,6 +58,8 @@ #include #include #include +#include +#include #include #include @@ -148,6 +150,8 @@ #include "ui/overlays/Cube3DOverlay.h" +#include "PluginContainerProxy.h" + // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. #if defined(Q_OS_WIN) @@ -301,7 +305,7 @@ bool setupEssentials(int& argc, char** argv) { // continuing to overburden Application.cpp Cube3DOverlay* _keyboardFocusHighlight{ nullptr }; int _keyboardFocusHighlightID{ -1 }; - +PluginContainer* _pluginContainer; Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QApplication(argc, argv), @@ -351,7 +355,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _applicationOverlay() { setInstance(this); - Plugin::setContainer(this); + + _pluginContainer = new PluginContainerProxy(); + Plugin::setContainer(_pluginContainer); #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif @@ -1262,6 +1268,7 @@ void Application::resizeGL() { // Possible change in aspect ratio loadViewFrustum(_myCamera, _viewFrustum); float fov = glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES); + // FIXME the aspect ratio for stereo displays is incorrect based on this. float aspectRatio = aspect(_renderResolution); _myCamera.setProjection(glm::perspective(fov, aspectRatio, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); } @@ -1421,7 +1428,15 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_Enter: case Qt::Key_Return: - Menu::getInstance()->triggerOption(MenuOption::AddressBar); + if (isOption) { + if (_window->isFullScreen()) { + _pluginContainer->unsetFullscreen(); + } else { + _pluginContainer->setFullscreen(nullptr); + } + } else { + Menu::getInstance()->triggerOption(MenuOption::AddressBar); + } break; case Qt::Key_B: @@ -4617,10 +4632,6 @@ void Application::checkSkeleton() { } } -bool Application::isForeground() { - return _isForeground && !getWindow()->isMinimized(); -} - void Application::activeChanged(Qt::ApplicationState state) { switch (state) { case Qt::ApplicationActive: @@ -4726,6 +4737,10 @@ const DisplayPlugin * Application::getActiveDisplayPlugin() const { return ((Application*)this)->getActiveDisplayPlugin(); } +bool _activatingDisplayPlugin{ false }; +QVector> _currentDisplayPluginActions; +QVector> _currentInputPluginActions; + static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); @@ -4747,9 +4762,6 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name)); } -static QVector> _currentDisplayPluginActions; -static bool _activatingDisplayPlugin{false}; - void Application::updateDisplayMode() { auto menu = Menu::getInstance(); auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); @@ -4816,7 +4828,7 @@ void Application::updateDisplayMode() { // Only show the hmd tools after the correct plugin has // been activated so that it's UI is setup correctly if (newPluginWantsHMDTools) { - showDisplayPluginsTools(); + _pluginContainer->showDisplayPluginsTools(); } if (oldDisplayPlugin) { @@ -4834,9 +4846,6 @@ void Application::updateDisplayMode() { Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } -static QVector> _currentInputPluginActions; - - static void addInputPluginToMenu(InputPluginPointer inputPlugin, bool active = false) { auto menu = Menu::getInstance(); QString name = inputPlugin->getName(); @@ -4910,42 +4919,6 @@ void Application::updateInputModes() { //} } -void Application::addMenu(const QString& menuName) { - Menu::getInstance()->addMenu(menuName); -} - -void Application::removeMenu(const QString& menuName) { - Menu::getInstance()->removeMenu(menuName); -} - -void Application::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { - auto menu = Menu::getInstance(); - MenuWrapper* parentItem = menu->getMenu(path); - QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); - connect(action, &QAction::triggered, [=] { - onClicked(action->isChecked()); - }); - action->setCheckable(checkable); - action->setChecked(checked); - if (_activatingDisplayPlugin) { - _currentDisplayPluginActions.push_back({ path, name }); - } else { - _currentInputPluginActions.push_back({ path, name }); - } -} - -void Application::removeMenuItem(const QString& menuName, const QString& menuItem) { - Menu::getInstance()->removeMenuItem(menuName, menuItem); -} - -bool Application::isOptionChecked(const QString& name) { - return Menu::getInstance()->isOptionChecked(name); -} - -void Application::setIsOptionChecked(const QString& path, bool checked) { - Menu::getInstance()->setIsOptionChecked(path, checked); -} - mat4 Application::getEyeProjection(int eye) const { if (isHMDMode()) { return getActiveDisplayPlugin()->getProjection((Eye)eye, _viewFrustum.getProjection()); @@ -4978,89 +4951,6 @@ mat4 Application::getHMDSensorPose() const { return mat4(); } -// FIXME there is a bug in the fullscreen setting, where leaving -// fullscreen does not restore the window frame, making it difficult -// or impossible to move or size the window. -// Additionally, setting fullscreen isn't hiding the menu on windows -// make it useless for stereoscopic modes. -void Application::setFullscreen(const QScreen* target) { - if (!_window->isFullScreen()) { - _savedGeometry = _window->geometry(); - } -#ifdef Q_OS_MAC - _window->setGeometry(target->availableGeometry()); -#endif - _window->windowHandle()->setScreen((QScreen*)target); - _window->showFullScreen(); - -#ifndef Q_OS_MAC - // also hide the QMainWindow's menuBar - QMenuBar* menuBar = _window->menuBar(); - if (menuBar) { - menuBar->setVisible(false); - } -#endif -} - -void Application::unsetFullscreen(const QScreen* avoid) { - _window->showNormal(); - - QRect targetGeometry = _savedGeometry; - if (avoid != nullptr) { - QRect avoidGeometry = avoid->geometry(); - if (avoidGeometry.contains(targetGeometry.topLeft())) { - QScreen* newTarget = primaryScreen(); - if (newTarget == avoid) { - foreach(auto screen, screens()) { - if (screen != avoid) { - newTarget = screen; - break; - } - } - } - targetGeometry = newTarget->availableGeometry(); - } - } -#ifdef Q_OS_MAC - QTimer* timer = new QTimer(); - timer->singleShot(2000, [=] { - _window->setGeometry(targetGeometry); - timer->deleteLater(); - }); -#else - _window->setGeometry(targetGeometry); -#endif - -#ifndef Q_OS_MAC - // also show the QMainWindow's menuBar - QMenuBar* menuBar = _window->menuBar(); - if (menuBar) { - menuBar->setVisible(true); - } -#endif -} - - -void Application::showDisplayPluginsTools() { - DependencyManager::get()->hmdTools(true); -} - -QGLWidget* Application::getPrimarySurface() { - return _glWidget; -} - -void Application::setActiveDisplayPlugin(const QString& pluginName) { - auto menu = Menu::getInstance(); - foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { - QString name = displayPlugin->getName(); - QAction* action = menu->getActionForOption(name); - if (pluginName == name) { - action->setChecked(true); - } - } - updateDisplayMode(); -} - void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index) { PalmData* palm; bool foundHand = false; diff --git a/interface/src/Application.h b/interface/src/Application.h index a1765109ce..6394aa12d0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -26,18 +26,18 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include -#include -#include #include #include "AudioClient.h" @@ -50,7 +50,6 @@ #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/MyAvatar.h" -#include #include "scripting/ControllerScriptingInterface.h" #include "scripting/DialogsManagerScriptingInterface.h" #include "scripting/WebWindowClass.h" @@ -132,7 +131,7 @@ class Application; typedef bool (Application::* AcceptURLMethod)(const QString &); -class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, PluginContainer { +class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface { Q_OBJECT friend class OctreePacketProcessor; @@ -280,23 +279,10 @@ public: virtual void endOverrideEnvironmentData() { _environment.endOverride(); } virtual qreal getDevicePixelRatio(); - // Plugin container support - virtual void addMenu(const QString& menuName); - virtual void removeMenu(const QString& menuName); - virtual void addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName); - virtual void removeMenuItem(const QString& menuName, const QString& menuItem); - virtual bool isOptionChecked(const QString& name); - virtual void setIsOptionChecked(const QString& path, bool checked); - virtual void setFullscreen(const QScreen* target) override; - virtual void unsetFullscreen(const QScreen* avoid) override; - virtual void showDisplayPluginsTools() override; - virtual QGLWidget* getPrimarySurface() override; - virtual bool isForeground() override; - void setActiveDisplayPlugin(const QString& pluginName); - DisplayPlugin * getActiveDisplayPlugin(); - const DisplayPlugin * getActiveDisplayPlugin() const; + DisplayPlugin* getActiveDisplayPlugin(); + const DisplayPlugin* getActiveDisplayPlugin() const; public: @@ -691,6 +677,8 @@ private: int _simsPerSecondReport = 0; quint64 _lastSimsPerSecondUpdate = 0; bool _isForeground = true; // starts out assumed to be in foreground + + friend class PluginContainerProxy; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 11bc38c85e..f99b3ba579 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -16,11 +16,13 @@ #include #include #include +#include #include #include #include #include + #include "Application.h" #include "AccountManager.h" #include "audio/AudioScope.h" @@ -220,7 +222,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); - MenuWrapper* displayMenu = addMenu("Display"); + MenuWrapper* displayMenu = addMenu(DisplayPlugin::MENU_PATH); { MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp new file mode 100644 index 0000000000..e172dbbd9e --- /dev/null +++ b/interface/src/PluginContainerProxy.cpp @@ -0,0 +1,161 @@ +#include "PluginContainerProxy.h" + +#include +#include + +#include +#include +#include + +#include "Application.h" +#include "MainWindow.h" +#include "GLCanvas.h" +#include "ui/DialogsManager.h" + +PluginContainerProxy::PluginContainerProxy() { + Plugin::setContainer(this); +} + +bool PluginContainerProxy::isForeground() { + return qApp->_isForeground && !qApp->getWindow()->isMinimized(); +} + +void PluginContainerProxy::addMenu(const QString& menuName) { + Menu::getInstance()->addMenu(menuName); +} + +void PluginContainerProxy::removeMenu(const QString& menuName) { + Menu::getInstance()->removeMenu(menuName); +} + +extern bool _activatingDisplayPlugin; +extern QVector> _currentDisplayPluginActions; +extern QVector> _currentInputPluginActions; +std::map _exclusiveGroups; + +QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { + auto menu = Menu::getInstance(); + MenuWrapper* parentItem = menu->getMenu(path); + QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); + if (!groupName.isEmpty()) { + QActionGroup* group{ nullptr }; + if (!_exclusiveGroups.count(groupName)) { + group = _exclusiveGroups[groupName] = new QActionGroup(menu); + group->setExclusive(true); + } else { + group = _exclusiveGroups[groupName]; + } + group->addAction(action); + } + connect(action, &QAction::triggered, [=] { + onClicked(action->isChecked()); + }); + action->setCheckable(checkable); + action->setChecked(checked); + if (_activatingDisplayPlugin) { + _currentDisplayPluginActions.push_back({ path, name }); + } else { + _currentInputPluginActions.push_back({ path, name }); + } + return action; +} + +void PluginContainerProxy::removeMenuItem(const QString& menuName, const QString& menuItem) { + Menu::getInstance()->removeMenuItem(menuName, menuItem); +} + +bool PluginContainerProxy::isOptionChecked(const QString& name) { + return Menu::getInstance()->isOptionChecked(name); +} + +void PluginContainerProxy::setIsOptionChecked(const QString& path, bool checked) { + Menu::getInstance()->setIsOptionChecked(path, checked); +} + +// FIXME there is a bug in the fullscreen setting, where leaving +// fullscreen does not restore the window frame, making it difficult +// or impossible to move or size the window. +// Additionally, setting fullscreen isn't hiding the menu on windows +// make it useless for stereoscopic modes. +void PluginContainerProxy::setFullscreen(const QScreen* target, bool hideMenu) { + auto _window = qApp->_window; + if (!_window->isFullScreen()) { + _savedGeometry = _window->geometry(); + } + if (nullptr == target) { + // FIXME target the screen where the window currently is + target = qApp->primaryScreen(); + } + + _window->setGeometry(target->availableGeometry()); + _window->windowHandle()->setScreen((QScreen*)target); + _window->showFullScreen(); + +#ifndef Q_OS_MAC + // also hide the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar && hideMenu) { + menuBar->setVisible(false); + } +#endif +} + +void PluginContainerProxy::unsetFullscreen(const QScreen* avoid) { + auto _window = qApp->_window; + _window->showNormal(); + + QRect targetGeometry = _savedGeometry; + if (avoid != nullptr) { + QRect avoidGeometry = avoid->geometry(); + if (avoidGeometry.contains(targetGeometry.topLeft())) { + QScreen* newTarget = qApp->primaryScreen(); + if (newTarget == avoid) { + foreach(auto screen, qApp->screens()) { + if (screen != avoid) { + newTarget = screen; + break; + } + } + } + targetGeometry = newTarget->availableGeometry(); + } + } +#ifdef Q_OS_MAC + QTimer* timer = new QTimer(); + timer->singleShot(2000, [=] { + _window->setGeometry(targetGeometry); + timer->deleteLater(); + }); +#else + _window->setGeometry(targetGeometry); +#endif + +#ifndef Q_OS_MAC + // also show the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + menuBar->setVisible(true); + } +#endif +} + + +void PluginContainerProxy::showDisplayPluginsTools() { + DependencyManager::get()->hmdTools(true); +} + +QGLWidget* PluginContainerProxy::getPrimarySurface() { + return qApp->_glWidget; +} + +void Application::setActiveDisplayPlugin(const QString& pluginName) { + auto menu = Menu::getInstance(); + foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { + QString name = displayPlugin->getName(); + QAction* action = menu->getActionForOption(name); + if (pluginName == name) { + action->setChecked(true); + } + } + updateDisplayMode(); +} diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h new file mode 100644 index 0000000000..e01cabc3b8 --- /dev/null +++ b/interface/src/PluginContainerProxy.h @@ -0,0 +1,30 @@ +#pragma once +#ifndef hifi_PluginContainerProxy_h +#define hifi_PluginContainerProxy_h + +#include +#include + +#include +#include + +class PluginContainerProxy : public QObject, PluginContainer { + Q_OBJECT + PluginContainerProxy(); + virtual void addMenu(const QString& menuName) override; + virtual void removeMenu(const QString& menuName) override; + virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override; + virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override; + virtual bool isOptionChecked(const QString& name) override; + virtual void setIsOptionChecked(const QString& path, bool checked); + virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = true) override; + virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override; + virtual void showDisplayPluginsTools() override; + virtual QGLWidget* getPrimarySurface() override; + virtual bool isForeground() override; + QRect _savedGeometry{ 10, 120, 800, 600 }; + + friend class Application; +}; + +#endif \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 018a09ff7e..da8deefa74 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -13,7 +13,6 @@ const QString Basic2DWindowOpenGLDisplayPlugin::NAME("2D Display"); -static const QString MENU_PATH = "Display"; static const QString FULLSCREEN = "Fullscreen"; const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index cd620d85a4..5840b3cbba 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -18,6 +18,8 @@ #include "oculus/OculusDisplayPlugin.h" #include "oculus/OculusLegacyDisplayPlugin.h" +const QString DisplayPlugin::MENU_PATH{ "Display" }; + // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class DisplayPluginList getDisplayPlugins() { DisplayPlugin* PLUGIN_POOL[] = { @@ -27,9 +29,11 @@ DisplayPluginList getDisplayPlugins() { #endif // Stereo modes - // FIXME fix stereo display plugins + + // SBS left/right new SideBySideStereoDisplayPlugin(), - //new InterleavedStereoDisplayPlugin(), + // Interleaved left/right + new InterleavedStereoDisplayPlugin(), // HMDs diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index 1a4166c0fa..8ee2d698a6 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -115,7 +115,7 @@ public: virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } - + static const QString MENU_PATH; signals: void recommendedFramebufferSizeChanged(const QSize & size); void requestRender(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 7269c9410c..0409899739 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -105,7 +105,7 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { void OpenGLDisplayPlugin::display( GLuint finalTexture, const glm::uvec2& sceneSize) { using namespace oglplus; - uvec2 size = getRecommendedRenderSize(); + uvec2 size = getSurfaceSize(); Context::Viewport(size.x, size.y); glBindTexture(GL_TEXTURE_2D, finalTexture); drawUnitQuad(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 9b6b92d8d4..3152500232 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -33,6 +33,7 @@ public: protected: virtual void customizeContext(); virtual void drawUnitQuad(); + virtual glm::uvec2 getSurfaceSize() const = 0; virtual void makeCurrent() = 0; virtual void doneCurrent() = 0; virtual void swapBuffers() = 0; diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp index 658aa2c767..ffea6605af 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp @@ -16,6 +16,10 @@ WindowOpenGLDisplayPlugin::WindowOpenGLDisplayPlugin() { } glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedRenderSize() const { + return getSurfaceSize(); +} + +glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { uvec2 result; if (_window) { result = toGlm(_window->geometry().size() * _window->devicePixelRatio()); @@ -23,6 +27,7 @@ glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedRenderSize() const { return result; } + glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedUiSize() const { uvec2 result; if (_window) { diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h index c75cf1484c..fc7691fc56 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h @@ -21,6 +21,7 @@ public: virtual void deactivate() override; protected: + virtual glm::uvec2 getSurfaceSize() const override final; virtual void makeCurrent() override; virtual void doneCurrent() override; virtual void swapBuffers() override; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 7d017f714d..23bc176d65 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -17,6 +17,42 @@ #include +static const char * INTERLEAVED_TEXTURED_VS = R"VS(#version 410 core +#pragma line __LINE__ + +in vec3 Position; +in vec2 TexCoord; + +out vec2 vTexCoord; + +void main() { + gl_Position = vec4(Position, 1); + vTexCoord = TexCoord; +} + +)VS"; + +static const char * INTERLEAVED_TEXTURED_FS = R"FS(#version 410 core +#pragma line __LINE__ + +uniform sampler2D sampler; +uniform ivec2 textureSize; + +in vec2 vTexCoord; +out vec4 FragColor; + +void main() { + ivec2 texCoord = ivec2(floor(vTexCoord * textureSize)); + texCoord.x /= 2; + int row = int(floor(gl_FragCoord.y)); + if (row % 2 > 0) { + texCoord.x += (textureSize.x / 2); + } + FragColor = texelFetch(sampler, texCoord, 0); //texture(sampler, texCoord); +} + +)FS"; + const QString InterleavedStereoDisplayPlugin::NAME("Interleaved Stereo Display"); const QString & InterleavedStereoDisplayPlugin::getName() const { @@ -29,5 +65,20 @@ InterleavedStereoDisplayPlugin::InterleavedStereoDisplayPlugin() { void InterleavedStereoDisplayPlugin::customizeContext() { StereoDisplayPlugin::customizeContext(); // Set up the stencil buffers? Or use a custom shader? - + compileProgram(_program, INTERLEAVED_TEXTURED_VS, INTERLEAVED_TEXTURED_FS); } + +glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const { + uvec2 result = WindowOpenGLDisplayPlugin::getRecommendedRenderSize(); + result.x *= 2; + result.y /= 2; + return result; +} + +void InterleavedStereoDisplayPlugin::display( + GLuint finalTexture, const glm::uvec2& sceneSize) { + using namespace oglplus; + _program->Bind(); + Uniform(*_program, "textureSize").SetValue(sceneSize); + WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize); +} \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 1dd38efed5..3044d91247 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -18,6 +18,9 @@ public: // initialize OpenGL context settings needed by the plugin virtual void customizeContext() override; + virtual glm::uvec2 getRecommendedRenderSize() const override; + void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp index 829385b209..1bdb02fd26 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -21,9 +21,6 @@ const QString SideBySideStereoDisplayPlugin::NAME("3D TV - Side by Side Stereo"); -static const QString MENU_PATH = "Display"; -static const QString FULLSCREEN = "Fullscreen"; - const QString & SideBySideStereoDisplayPlugin::getName() const { return NAME; } @@ -31,20 +28,10 @@ const QString & SideBySideStereoDisplayPlugin::getName() const { SideBySideStereoDisplayPlugin::SideBySideStereoDisplayPlugin() { } -void SideBySideStereoDisplayPlugin::activate() { - CONTAINER->addMenu(MENU_PATH); - CONTAINER->addMenuItem(MENU_PATH, FULLSCREEN, - [this](bool clicked) { - if (clicked) { - CONTAINER->setFullscreen(getFullscreenTarget()); - } else { - CONTAINER->unsetFullscreen(); - } - }, true, false); - StereoDisplayPlugin::activate(); +glm::uvec2 SideBySideStereoDisplayPlugin::getRecommendedRenderSize() const { + uvec2 result = WindowOpenGLDisplayPlugin::getRecommendedRenderSize(); + result.x *= 2; + return result; } -// FIXME target the screen the window is currently on -QScreen* SideBySideStereoDisplayPlugin::getFullscreenTarget() { - return qApp->primaryScreen(); -} + diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h index 3a764d9f4e..9f8440227f 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h @@ -16,8 +16,7 @@ class SideBySideStereoDisplayPlugin : public StereoDisplayPlugin { public: SideBySideStereoDisplayPlugin(); virtual const QString& getName() const override; - virtual void activate() override; + virtual glm::uvec2 getRecommendedRenderSize() const override; private: - QScreen* getFullscreenTarget(); static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 20613d6a2c..43c3b727c4 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -10,11 +10,14 @@ #include #include +#include #include #include #include #include +#include +#include StereoDisplayPlugin::StereoDisplayPlugin() { } @@ -29,11 +32,14 @@ const float DEFAULT_IPD = 0.064f; const float HALF_DEFAULT_IPD = DEFAULT_IPD / 2.0f; glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { - // FIXME check for mono eye and provide a combined matrix, needed for proper - // culling // Refer to http://www.nvidia.com/content/gtc-2010/pdfs/2010_gtc2010.pdf on creating // stereo projection matrices. Do NOT use "toe-in", use translation. + if (eye == Mono) { + // FIXME provide a combined matrix, needed for proper culling + return baseProjection; + } + float nearZ = DEFAULT_NEAR_CLIP; // near clipping plane float screenZ = 0.25f; // screen projection plane // FIXME verify this is the right calculation @@ -52,10 +58,36 @@ glm::mat4 StereoDisplayPlugin::getEyePose(Eye eye) const { return glm::translate(mat4(), vec3(modelviewShift, 0, 0)); } +std::vector _screenActions; void StereoDisplayPlugin::activate() { + auto screens = qApp->screens(); + _screenActions.resize(screens.size()); + for (int i = 0; i < screens.size(); ++i) { + auto screen = screens.at(i); + QString name = QString("Screen %1: %2").arg(i + 1).arg(screen->name()); + bool checked = false; + if (screen == qApp->primaryScreen()) { + checked = true; + } + auto action = CONTAINER->addMenuItem(MENU_PATH, name, + [this](bool clicked) { updateScreen(); }, true, checked, "Screens"); + _screenActions[i] = action; + } + CONTAINER->setFullscreen(qApp->primaryScreen()); WindowOpenGLDisplayPlugin::activate(); - // FIXME there is a bug in the fullscreen setting, see - // Application::setFullscreen - //CONTAINER->setFullscreen(qApp->primaryScreen()); - // FIXME Add menu items +} + +void StereoDisplayPlugin::updateScreen() { + for (int i = 0; i < _screenActions.size(); ++i) { + if (_screenActions[i]->isChecked()) { + CONTAINER->setFullscreen(qApp->screens().at(i)); + break; + } + } +} + +void StereoDisplayPlugin::deactivate() { + _screenActions.clear(); + CONTAINER->unsetFullscreen(); + WindowOpenGLDisplayPlugin::deactivate(); } diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index b0f0414de2..2009c32e1c 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -17,10 +17,12 @@ public: virtual bool isSupported() const override final; virtual void activate() override; + virtual void deactivate() override; virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; virtual glm::mat4 getEyePose(Eye eye) const override; protected: + void updateScreen(); float _ipd{ 0.064f }; }; diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h new file mode 100644 index 0000000000..1a9298d13c --- /dev/null +++ b/libraries/plugins/src/plugins/Forward.h @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include +#include + +class DisplayPlugin; +class InputPlugin; +class Plugin; +class PluginContainer; +class PluginManager; + +using DisplayPluginPointer = QSharedPointer; +using DisplayPluginList = QVector; +using InputPluginPointer = QSharedPointer; +using InputPluginList = QVector; diff --git a/libraries/plugins/src/plugins/Plugin.cpp b/libraries/plugins/src/plugins/Plugin.cpp index e0cacda474..ffcc682ebd 100644 --- a/libraries/plugins/src/plugins/Plugin.cpp +++ b/libraries/plugins/src/plugins/Plugin.cpp @@ -1,3 +1,10 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// #include "Plugin.h" PluginContainer* Plugin::CONTAINER{ nullptr }; diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index a2edbc8236..eac355b47d 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -1,9 +1,16 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// #pragma once #include #include -class PluginContainer; +#include "Forward.h" class Plugin : public QObject { public: diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index 7f6346a181..c2cba23392 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -7,9 +7,10 @@ // #pragma once -#include #include +#include +class QAction; class QGLWidget; class QScreen; @@ -18,11 +19,11 @@ public: PluginContainer(); virtual void addMenu(const QString& menuName) = 0; virtual void removeMenu(const QString& menuName) = 0; - virtual void addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; + virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; virtual void removeMenuItem(const QString& menuName, const QString& menuItem) = 0; virtual bool isOptionChecked(const QString& name) = 0; virtual void setIsOptionChecked(const QString& path, bool checked) = 0; - virtual void setFullscreen(const QScreen* targetScreen) = 0; + virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = false) = 0; virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) = 0; virtual void showDisplayPluginsTools() = 0; virtual QGLWidget* getPrimarySurface() = 0; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index ffee9905a0..3a71700c9e 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -1,7 +1,13 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// #include "PluginManager.h" #include - PluginManager* PluginManager::getInstance() { static PluginManager _manager; return &_manager; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 88dba3366e..b7ec453814 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -1,17 +1,13 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// #pragma once -#include "Plugin.h" -#include -#include -#include - -class DisplayPlugin; -class InputPlugin; - -using DisplayPluginPointer = QSharedPointer; -using DisplayPluginList = QVector; -using InputPluginPointer = QSharedPointer; -using InputPluginList = QVector; +#include "Forward.h" class PluginManager : public QObject { public: From 1f08444369c400d7a1a84ebb91d4faa439bb475a Mon Sep 17 00:00:00 2001 From: Nex Pro Date: Sun, 23 Aug 2015 16:21:20 +0100 Subject: [PATCH 143/170] Modified the world box to render axes in negative space Also Modified renderDashedLine to accept params for dash len and gap len so we can display negative axes as dashed lines with 500mm dashes and gaps. --- interface/src/Util.cpp | 18 +++++++++++++++++- libraries/render-utils/src/GeometryCache.cpp | 20 ++++++++++---------- libraries/render-utils/src/GeometryCache.h | 11 ++++++----- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 3a01367fc7..cb986e06d5 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -35,17 +35,33 @@ using namespace std; void renderWorldBox(gpu::Batch& batch) { auto geometryCache = DependencyManager::get(); - // Show edge of world + // Show center of world static const glm::vec3 red(1.0f, 0.0f, 0.0f); static const glm::vec3 green(0.0f, 1.0f, 0.0f); static const glm::vec3 blue(0.0f, 0.0f, 1.0f); static const glm::vec3 grey(0.5f, 0.5f, 0.5f); + static const glm::vec4 DASHED_RED(1.0f, 0.0f, 0.0f, 1.0f); + static const glm::vec4 DASHED_GREEN(0.0f, 1.0f, 0.0f, 1.0f); + static const glm::vec4 DASHED_BLUE(0.0f, 0.0f, 1.0f, 1.0f); + static const float DASH_LENGTH = 0.5; + static const float GAP_LENGTH = 0.5; auto transform = Transform{}; + batch.setModelTransform(transform); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(TREE_SCALE, 0.0f, 0.0f), red); + geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-TREE_SCALE, 0.0f, 0.0f), DASHED_RED, + DASH_LENGTH, GAP_LENGTH); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, TREE_SCALE, 0.0f), green); + geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, -TREE_SCALE, 0.0f), DASHED_GREEN, + DASH_LENGTH, GAP_LENGTH); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, TREE_SCALE), blue); + geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -TREE_SCALE), DASHED_BLUE, + DASH_LENGTH, GAP_LENGTH); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, TREE_SCALE), glm::vec3(TREE_SCALE, 0.0f, TREE_SCALE), grey); geometryCache->renderLine(batch, glm::vec3(TREE_SCALE, 0.0f, TREE_SCALE), glm::vec3(TREE_SCALE, 0.0f, 0.0f), grey); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 31b030f75a..6801ee8cdc 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -64,7 +64,7 @@ void GeometryCache::renderSphere(gpu::Batch& batch, float radius, int slices, in int vertices = slices * (stacks - 1) + 2; int indices = slices * (stacks - 1) * NUM_VERTICES_PER_TRIANGULATED_QUAD; - + if ((registered && (!_registeredSphereVertices.contains(id) || _lastRegisteredSphereVertices[id] != radiusKey)) || (!registered && !_sphereVertices.contains(radiusKey))) { @@ -74,7 +74,7 @@ void GeometryCache::renderSphere(gpu::Batch& batch, float radius, int slices, in qCDebug(renderutils) << "renderSphere()... RELEASING REGISTERED VERTICES BUFFER"; #endif } - + auto verticesBuffer = std::make_shared(); if (registered) { _registeredSphereVertices[id] = verticesBuffer; @@ -128,7 +128,7 @@ void GeometryCache::renderSphere(gpu::Batch& batch, float radius, int slices, in qCDebug(renderutils) << "renderSphere()... REUSING PREVIOUSLY REGISTERED VERTICES BUFFER"; } #endif - + if ((registered && (!_registeredSphereIndices.contains(id) || _lastRegisteredSphereIndices[id] != slicesStacksKey)) || (!registered && !_sphereIndices.contains(slicesStacksKey))) { @@ -1321,7 +1321,9 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec3& topLeft, cons batch.draw(gpu::TRIANGLE_STRIP, 4, 0); } -void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, int id) { +void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, + const float dash_length, const float gap_length, int id) { + bool registered = (id != UNKNOWN_ID); Vec3PairVec2Pair key(Vec3Pair(start, end), Vec2Pair(glm::vec2(color.x, color.y), glm::vec2(color.z, color.w))); BatchItemDetails& details = registered ? _registeredDashedLines[id] : _dashedLines[key]; @@ -1345,16 +1347,14 @@ void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, ((int(color.w * 255.0f) & 0xFF) << 24); // draw each line segment with appropriate gaps - const float DASH_LENGTH = 0.05f; - const float GAP_LENGTH = 0.025f; - const float SEGMENT_LENGTH = DASH_LENGTH + GAP_LENGTH; + const float SEGMENT_LENGTH = dash_length + gap_length; float length = glm::distance(start, end); float segmentCount = length / SEGMENT_LENGTH; int segmentCountFloor = (int)glm::floor(segmentCount); glm::vec3 segmentVector = (end - start) / segmentCount; - glm::vec3 dashVector = segmentVector / SEGMENT_LENGTH * DASH_LENGTH; - glm::vec3 gapVector = segmentVector / SEGMENT_LENGTH * GAP_LENGTH; + glm::vec3 dashVector = segmentVector / SEGMENT_LENGTH * dash_length; + glm::vec3 gapVector = segmentVector / SEGMENT_LENGTH * gap_length; const int FLOATS_PER_VERTEX = 3; details.vertices = (segmentCountFloor + 1) * 2; @@ -1388,7 +1388,7 @@ void GeometryCache::renderDashedLine(gpu::Batch& batch, const glm::vec3& start, *(vertex++) = point.y; *(vertex++) = point.z; *(colorDataAt++) = compactColor; - + for (int i = 0; i < segmentCountFloor; i++) { point += dashVector; *(vertex++) = point.x; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index f70eae6380..2545822c07 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -179,7 +179,8 @@ public: void renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, const glm::vec4& color1, const glm::vec4& color2, int id = UNKNOWN_ID); - void renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, int id = UNKNOWN_ID); + void renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, + const float dash_length = 0.05f, const float gap_length = 0.025f, int id = UNKNOWN_ID); void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, const glm::vec3& color, int id = UNKNOWN_ID) { renderLine(batch, p1, p2, glm::vec4(color, 1.0f), id); } @@ -189,9 +190,9 @@ public: void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, - const glm::vec3& color1, const glm::vec3& color2, int id = UNKNOWN_ID) + const glm::vec3& color1, const glm::vec3& color2, int id = UNKNOWN_ID) { renderLine(batch, p1, p2, glm::vec4(color1, 1.0f), glm::vec4(color2, 1.0f), id); } - + void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, const glm::vec4& color1, const glm::vec4& color2, int id = UNKNOWN_ID); @@ -216,7 +217,7 @@ protected: private: GeometryCache(); virtual ~GeometryCache(); - + typedef QPair IntPair; typedef QPair VerticesIndices; @@ -247,7 +248,7 @@ private: ~BatchItemDetails(); void clear(); }; - + QHash _coneVBOs; int _nextID; From 3e8970cc1ef5ca55f553f99049dd328b950a565c Mon Sep 17 00:00:00 2001 From: Nex Pro Date: Sun, 23 Aug 2015 16:49:39 +0100 Subject: [PATCH 144/170] corrected declaration of float constants DASH_LENGTH and GAP_LENGTH as per style guide. --- interface/src/Util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 217a38e7b9..fa07864e75 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -44,8 +44,8 @@ void renderWorldBox(gpu::Batch& batch) { static const glm::vec4 DASHED_RED(1.0f, 0.0f, 0.0f, 1.0f); static const glm::vec4 DASHED_GREEN(0.0f, 1.0f, 0.0f, 1.0f); static const glm::vec4 DASHED_BLUE(0.0f, 0.0f, 1.0f, 1.0f); - static const float DASH_LENGTH = 0.5; - static const float GAP_LENGTH = 0.5; + static const float DASH_LENGTH = 1.0; + static const float GAP_LENGTH = 1.0; auto transform = Transform{}; batch.setModelTransform(transform); From 0ef2def56eb823fec277f34a9dc4680131054eef Mon Sep 17 00:00:00 2001 From: Nex Pro Date: Sun, 23 Aug 2015 16:52:12 +0100 Subject: [PATCH 145/170] changed default DASH_LENGTH and GAP_LENGTH to 1.0f --- interface/src/Util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index fa07864e75..695a8be5e7 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -44,8 +44,8 @@ void renderWorldBox(gpu::Batch& batch) { static const glm::vec4 DASHED_RED(1.0f, 0.0f, 0.0f, 1.0f); static const glm::vec4 DASHED_GREEN(0.0f, 1.0f, 0.0f, 1.0f); static const glm::vec4 DASHED_BLUE(0.0f, 0.0f, 1.0f, 1.0f); - static const float DASH_LENGTH = 1.0; - static const float GAP_LENGTH = 1.0; + static const float DASH_LENGTH = 1.0f; + static const float GAP_LENGTH = 1.0f; auto transform = Transform{}; batch.setModelTransform(transform); From 748625399e8ed37e46e1801b5984ea107862d77f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sun, 23 Aug 2015 17:59:58 +0200 Subject: [PATCH 146/170] "View -> World Axes" menu option --- interface/src/Application.cpp | 2 +- interface/src/Menu.cpp | 2 ++ interface/src/Menu.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aa18e602bf..ec8a6b87f9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3340,7 +3340,7 @@ namespace render { template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { - if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { PerformanceTimer perfTimer("worldBox"); auto& batch = *args->_batch; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 11bc38c85e..af80320157 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -294,6 +294,8 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::StandingHMDSensorMode, 0, false, avatar, SLOT(updateStandingHMDModeFromMenu())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::WorldAxes); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats); addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 278da363d1..4bd1e7f664 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -294,6 +294,7 @@ namespace MenuOption { const QString VisibleToEveryone = "Everyone"; const QString VisibleToFriends = "Friends"; const QString VisibleToNoOne = "No one"; + const QString WorldAxes = "World Axes"; } #endif // hifi_Menu_h From 53a63fed792253e0bdb5622940ee5ace6fb41944 Mon Sep 17 00:00:00 2001 From: Nex Pro Date: Sun, 23 Aug 2015 17:16:23 +0100 Subject: [PATCH 147/170] Changed constants red, green, blue and grey to uppercase as per style guide. --- interface/src/Util.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 695a8be5e7..4aeb0f6ac3 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -36,10 +36,10 @@ void renderWorldBox(gpu::Batch& batch) { auto geometryCache = DependencyManager::get(); // Show center of world - static const glm::vec3 red(1.0f, 0.0f, 0.0f); - static const glm::vec3 green(0.0f, 1.0f, 0.0f); - static const glm::vec3 blue(0.0f, 0.0f, 1.0f); - static const glm::vec3 grey(0.5f, 0.5f, 0.5f); + static const glm::vec3 RED(1.0f, 0.0f, 0.0f); + static const glm::vec3 GREEN(0.0f, 1.0f, 0.0f); + static const glm::vec3 BLUE(0.0f, 0.0f, 1.0f); + static const glm::vec3 GREY(0.5f, 0.5f, 0.5f); static const glm::vec4 DASHED_RED(1.0f, 0.0f, 0.0f, 1.0f); static const glm::vec4 DASHED_GREEN(0.0f, 1.0f, 0.0f, 1.0f); @@ -50,42 +50,42 @@ void renderWorldBox(gpu::Batch& batch) { batch.setModelTransform(transform); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), red); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), RED); geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-HALF_TREE_SCALE, 0.0f, 0.0f), DASHED_RED, DASH_LENGTH, GAP_LENGTH); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, HALF_TREE_SCALE, 0.0f), green); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, HALF_TREE_SCALE, 0.0f), GREEN); geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, -HALF_TREE_SCALE, 0.0f), DASHED_GREEN, DASH_LENGTH, GAP_LENGTH); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), blue); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), BLUE); geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -HALF_TREE_SCALE), DASHED_BLUE, DASH_LENGTH, GAP_LENGTH); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), grey); - geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), grey); + geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), GREY); // Draw meter markers along the 3 axis to help with measuring things const float MARKER_DISTANCE = 1.0f; const float MARKER_RADIUS = 0.05f; - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, red); + geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, RED); transform.setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, 0.0f)); batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, red); + geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, RED); transform.setTranslation(glm::vec3(0.0f, MARKER_DISTANCE, 0.0f)); batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, green); + geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, GREEN); transform.setTranslation(glm::vec3(0.0f, 0.0f, MARKER_DISTANCE)); batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, blue); + geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, BLUE); transform.setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, MARKER_DISTANCE)); batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, grey); + geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, GREY); } // Return a random vector of average length 1 From b7237fb5ca6eeaf8986839369549d0b08f9cff6e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 23 Aug 2015 09:32:58 -0700 Subject: [PATCH 148/170] Fixing build problem with 3d connexion --- interface/src/devices/3DConnexionClient.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/src/devices/3DConnexionClient.cpp b/interface/src/devices/3DConnexionClient.cpp index ff54804bef..52b4dce68d 100755 --- a/interface/src/devices/3DConnexionClient.cpp +++ b/interface/src/devices/3DConnexionClient.cpp @@ -185,7 +185,8 @@ void ConnexionClient::destroy() { ConnexionData& connexiondata = ConnexionData::getInstance(); int deviceid = connexiondata.getDeviceID(); connexiondata.setDeviceID(0); - Application::getUserInputMapper()->removeDevice(deviceid); + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(deviceid); } #define LOGITECH_VENDOR_ID 0x46d @@ -289,14 +290,15 @@ unsigned short HidToVirtualKey(unsigned long pid, unsigned short hidKeyCode) { bool ConnexionClient::RawInputEventFilter(void* msg, long* result) { ConnexionData& connexiondata = ConnexionData::getInstance(); + auto userInputMapper = DependencyManager::get(); if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { - connexiondata.registerToUserInputMapper(*Application::getUserInputMapper()); - connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper()); + connexiondata.registerToUserInputMapper(*userInputMapper); + connexiondata.assignDefaultInputMapping(*userInputMapper); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); } else if (!Is3dmouseAttached() && connexiondata.getDeviceID() != 0) { int deviceid = connexiondata.getDeviceID(); connexiondata.setDeviceID(0); - Application::getUserInputMapper()->removeDevice(deviceid); + userInputMapper->removeDevice(deviceid); } if (!Is3dmouseAttached()) { From 63f01736653e13ff677f20388976612859b0a88a Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sun, 23 Aug 2015 19:52:29 +0200 Subject: [PATCH 149/170] extend world boundaries --- interface/src/Util.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 4aeb0f6ac3..7ee38d7de0 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -40,6 +40,7 @@ void renderWorldBox(gpu::Batch& batch) { static const glm::vec3 GREEN(0.0f, 1.0f, 0.0f); static const glm::vec3 BLUE(0.0f, 0.0f, 1.0f); static const glm::vec3 GREY(0.5f, 0.5f, 0.5f); + static const glm::vec4 GREY4(0.5f, 0.5f, 0.5f, 1.0f); static const glm::vec4 DASHED_RED(1.0f, 0.0f, 0.0f, 1.0f); static const glm::vec4 DASHED_GREEN(0.0f, 1.0f, 0.0f, 1.0f); @@ -62,8 +63,25 @@ void renderWorldBox(gpu::Batch& batch) { geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -HALF_TREE_SCALE), DASHED_BLUE, DASH_LENGTH, GAP_LENGTH); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), GREY); + // X center boundaries + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + + // Z center boundaries + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + + // Center boundaries + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + + geometryCache->renderWireCube(batch, TREE_SCALE, GREY4); // Draw meter markers along the 3 axis to help with measuring things const float MARKER_DISTANCE = 1.0f; From bbd6f25ff7e95a0cc2a9b39ae9da77053b4b20e6 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sun, 23 Aug 2015 20:01:58 +0200 Subject: [PATCH 150/170] style fixes --- interface/src/Util.cpp | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 7ee38d7de0..dad34e9243 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -64,23 +64,35 @@ void renderWorldBox(gpu::Batch& batch) { DASH_LENGTH, GAP_LENGTH); // X center boundaries - geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), GREY); - geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); - geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); - geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), + glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), + glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), + glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), + glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); // Z center boundaries - geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), + glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), + glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), + glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), + glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); // Center boundaries - geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); - + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), + glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), + glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), + glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), + glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderWireCube(batch, TREE_SCALE, GREY4); // Draw meter markers along the 3 axis to help with measuring things From 3f4323e084c69e74ef5d5010b477ee6b06776202 Mon Sep 17 00:00:00 2001 From: Nex Pro Date: Mon, 24 Aug 2015 16:07:07 +0100 Subject: [PATCH 151/170] Added overload for renderDashedLine --- libraries/render-utils/src/GeometryCache.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index d7d9be0d2e..e9fe669aee 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -182,9 +182,13 @@ public: void renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, const glm::vec4& color1, const glm::vec4& color2, int id = UNKNOWN_ID); - + void renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, - const float dash_length = 0.05f, const float gap_length = 0.025f, int id = UNKNOWN_ID); + int id = UNKNOWN_ID) + { renderDashedLine(batch, start, end, color, 0.05f, 0.025f, id); } + + void renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, + const float dash_length, const float gap_length, int id = UNKNOWN_ID); void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, const glm::vec3& color, int id = UNKNOWN_ID) { renderLine(batch, p1, p2, glm::vec4(color, 1.0f), id); } @@ -192,12 +196,11 @@ public: void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, const glm::vec4& color, int id = UNKNOWN_ID) { renderLine(batch, p1, p2, color, color, id); } - - void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, + void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, const glm::vec3& color1, const glm::vec3& color2, int id = UNKNOWN_ID) { renderLine(batch, p1, p2, glm::vec4(color1, 1.0f), glm::vec4(color2, 1.0f), id); } - void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, + void renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm::vec2& p2, const glm::vec4& color1, const glm::vec4& color2, int id = UNKNOWN_ID); void updateVertices(int id, const QVector& points, const glm::vec4& color); From 47b8ab373cd9cbae48a81ee04cfac37a228f69c5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Aug 2015 11:15:30 -0700 Subject: [PATCH 152/170] Fix for RenderableParticleEffectEntity crash. The render item payload now keeps a shared_ptr to the entity. --- .../src/RenderableParticleEffectEntityItem.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 63b83badb7..d9d4ec4a5b 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -30,9 +30,12 @@ public: typedef Payload::DataPointer Pointer; typedef RenderableParticleEffectEntityItem::Vertex Vertex; - ParticlePayload() : _vertexFormat(std::make_shared()), - _vertexBuffer(std::make_shared()), - _indexBuffer(std::make_shared()) { + ParticlePayload(EntityItemPointer entity) : + _entity(entity), + _vertexFormat(std::make_shared()), + _vertexBuffer(std::make_shared()), + _indexBuffer(std::make_shared()) { + _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element::VEC3F_XYZ, 0); _vertexFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), offsetof(Vertex, uv)); _vertexFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, offsetof(Vertex, rgba)); @@ -79,6 +82,7 @@ public: } protected: + EntityItemPointer _entity; Transform _modelTransform; AABox _bound; gpu::PipelinePointer _pipeline; @@ -130,7 +134,7 @@ bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) { - auto particlePayload = std::shared_ptr(new ParticlePayload()); + auto particlePayload = std::shared_ptr(new ParticlePayload(shared_from_this())); particlePayload->setPipeline(_untexturedPipeline); _renderItemId = scene->allocateID(); auto renderData = ParticlePayload::Pointer(particlePayload); From 691cb48a08af1f36aeb0707aac17e2c0d27d5fac Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Mon, 24 Aug 2015 11:26:56 -0700 Subject: [PATCH 153/170] Fixed to use standard shaders from render-utils --- tests/gpu-test/src/gputest_shaders.h | 124 ----------------------- tests/gpu-test/src/gputest_simple_frag.h | 105 ------------------- tests/gpu-test/src/gputest_simple_vert.h | 113 --------------------- tests/gpu-test/src/main.cpp | 9 +- tests/gpu-test/src/simple.slf | 29 ------ tests/gpu-test/src/simple.slv | 36 ------- 6 files changed, 4 insertions(+), 412 deletions(-) delete mode 100644 tests/gpu-test/src/gputest_shaders.h delete mode 100644 tests/gpu-test/src/gputest_simple_frag.h delete mode 100644 tests/gpu-test/src/gputest_simple_vert.h delete mode 100644 tests/gpu-test/src/simple.slf delete mode 100644 tests/gpu-test/src/simple.slv diff --git a/tests/gpu-test/src/gputest_shaders.h b/tests/gpu-test/src/gputest_shaders.h deleted file mode 100644 index 376ed3c3f3..0000000000 --- a/tests/gpu-test/src/gputest_shaders.h +++ /dev/null @@ -1,124 +0,0 @@ -// -// gputest_shaders.h -// hifi -// -// Created by Seiji Emery on 8/3/15. -// -// - -#ifndef hifi_gputest_shaders_h -#define hifi_gputest_shaders_h - - -const std::string & standardVertexShader() { - static std::string src = R"( - - in vec4 inPosition; - in vec4 inNormal; - in vec4 inColor; - in vec4 inTexCoord0; - in vec4 inTangent; - in vec4 inSkinClusterIndex; - in vec4 inSkinClusterWeight; - in vec4 inTexCoord1; - - struct TransformObject { - mat4 _model; - mat4 _modelInverse; - }; - - struct TransformCamera { - mat4 _view; - mat4 _viewInverse; - mat4 _projectionViewUntranslated; - mat4 _projection; - mat4 _projectionInverse; - vec4 _viewport; - }; - - uniform transformObjectBuffer { - TransformObject _object; - }; - TransformObject getTransformObject() { - return _object; - } - - uniform transformCameraBuffer { - TransformCamera _camera; - }; - TransformCamera getTransformCamera() { - return _camera; - } - - - const int MAX_TEXCOORDS = 2; - - uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; - - out vec4 _position; - out vec3 _normal; - out vec3 _color; - out vec2 _texCoord0; - - )"; - return src; -} - - - - - -const std::string & basicVertexShader () { - static std::string src = R"( - // Basic forward-rendered shading w/ a single directional light source. - - #version 410 - - // I/O - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec3 vertexNormal; - - out vec3 outColor; - - // Light info - uniform vec4 lightPosition; - uniform vec3 kd; // diffuse reflectivity - uniform vec3 ld; // light source intensity - - // Model transforms - uniform mat4 modelViewMatrix; - uniform mat3 normalMatrix; - uniform mat4 projectionMatrix; - uniform mat4 mvp; - - void main (void) { - vec3 norm = normalize(normalMatrix * vertexNormal); - vec4 eyePos = modelViewMatrix * vec4(vertexPosition, 0); - vec3 s = normalize(vec3(lightPosition - eyePos)); - - outColor = ld * kd * max(dot(s, norm), 0.0); - - gl_Position = mvp * vec4(vertexPosition, 1.0); - } - - )"; - return src; -} - -const std::string & basicFragmentShader () { - static std::string src = R"( - #version 410 - - // Just pass interpolated color value along - in vec3 outColor; - - layout (location = 0) out vec4 fragColor; - - void main(void) { - fragColor = vec4(outColor, 1.0); - } - - )"; - return src; -} -#endif diff --git a/tests/gpu-test/src/gputest_simple_frag.h b/tests/gpu-test/src/gputest_simple_frag.h deleted file mode 100644 index 324e26b082..0000000000 --- a/tests/gpu-test/src/gputest_simple_frag.h +++ /dev/null @@ -1,105 +0,0 @@ -// File generated by Scribe Wed Aug 5 16:50:24 2015 -#ifndef scribe_simple_frag_h -#define scribe_simple_frag_h - -const char simple_frag[] = R"SCRIBE(#version 410 core -// Generated on Wed Aug 5 16:50:24 2015 -// -// simple.frag -// fragment shader -// -// Created by Andrzej Kapolka on 9/15/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 -// - -layout(location = 0) out vec4 _fragColor0; -layout(location = 1) out vec4 _fragColor1; -layout(location = 2) out vec4 _fragColor2; - -// the glow intensity -uniform float glowIntensity; - -// the alpha threshold -uniform float alphaThreshold; - -float evalOpaqueFinalAlpha(float alpha, float mapAlpha) { - return mix(alpha * glowIntensity, 1.0 - alpha * glowIntensity, step(mapAlpha, alphaThreshold)); -} - -const vec3 DEFAULT_SPECULAR = vec3(0.1); -const float DEFAULT_SHININESS = 10; - -void packDeferredFragment(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { - if (alpha != glowIntensity) { - discard; - } - _fragColor0 = vec4(diffuse.rgb, alpha); - _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - _fragColor2 = vec4(specular, shininess / 128.0); -} - -void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess, vec3 emissive) { - if (alpha != glowIntensity) { - discard; - } - - _fragColor0 = vec4(diffuse.rgb, alpha); - //_fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5); - _fragColor2 = vec4(emissive, shininess / 128.0); -} - -void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { - if (alpha <= alphaThreshold) { - discard; - } - - _fragColor0 = vec4(diffuse.rgb, alpha); - // _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - // _fragColor2 = vec4(specular, shininess / 128.0); -} - - - - - -struct Material { - vec4 _diffuse; - vec4 _specular; - vec4 _emissive; - vec4 _spare; -}; - -uniform materialBuffer { - Material _mat; -}; - -Material getMaterial() { - return _mat; -} - -float getMaterialOpacity(Material m) { return m._diffuse.a; } -vec3 getMaterialDiffuse(Material m) { return m._diffuse.rgb; } -vec3 getMaterialSpecular(Material m) { return m._specular.rgb; } -float getMaterialShininess(Material m) { return m._specular.a; } - - -// the interpolated normal -in vec3 _normal; -in vec3 _color; - -void main(void) { - Material material = getMaterial(); - packDeferredFragment( - normalize(_normal.xyz), - glowIntensity, - _color.rgb, - DEFAULT_SPECULAR, DEFAULT_SHININESS); -} - -)SCRIBE"; - -#endif diff --git a/tests/gpu-test/src/gputest_simple_vert.h b/tests/gpu-test/src/gputest_simple_vert.h deleted file mode 100644 index 419c91b7c2..0000000000 --- a/tests/gpu-test/src/gputest_simple_vert.h +++ /dev/null @@ -1,113 +0,0 @@ -// File generated by Scribe Wed Aug 5 16:50:23 2015 -#ifndef scribe_simple_vert_h -#define scribe_simple_vert_h - -const char simple_vert[] = R"SCRIBE(#version 410 core -// Generated on Wed Aug 5 16:50:23 2015 -// -// simple.vert -// vertex shader -// -// Created by Andrzej Kapolka on 9/15/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 -// - -in vec4 inPosition; -in vec4 inNormal; -in vec4 inColor; -in vec4 inTexCoord0; -in vec4 inTangent; -in vec4 inSkinClusterIndex; -in vec4 inSkinClusterWeight; -in vec4 inTexCoord1; - - - - - - - - - - - - - - - - - - - - - - -struct TransformObject { - mat4 _model; - mat4 _modelInverse; -}; - -struct TransformCamera { - mat4 _view; - mat4 _viewInverse; - mat4 _projectionViewUntranslated; - mat4 _projection; - mat4 _projectionInverse; - vec4 _viewport; -}; - -uniform transformObjectBuffer { - TransformObject _object; -}; -TransformObject getTransformObject() { - return _object; -} - -uniform transformCameraBuffer { - TransformCamera _camera; -}; -TransformCamera getTransformCamera() { - return _camera; -} - - -// the interpolated normal - -out vec3 _normal; -out vec3 _color; -out vec2 _texCoord0; - -void main(void) { - _color = inColor.rgb; - _texCoord0 = inTexCoord0.st; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - - - { // transformModelToClipPos - vec4 _eyepos = (obj._model * inPosition) + vec4(-inPosition.w * cam._viewInverse[3].xyz, 0.0); - gl_Position = cam._projectionViewUntranslated * _eyepos; - } - - - { // transformModelToEyeDir - vec3 mr0 = vec3(obj._modelInverse[0].x, obj._modelInverse[1].x, obj._modelInverse[2].x); - vec3 mr1 = vec3(obj._modelInverse[0].y, obj._modelInverse[1].y, obj._modelInverse[2].y); - vec3 mr2 = vec3(obj._modelInverse[0].z, obj._modelInverse[1].z, obj._modelInverse[2].z); - - vec3 mvc0 = vec3(dot(cam._viewInverse[0].xyz, mr0), dot(cam._viewInverse[0].xyz, mr1), dot(cam._viewInverse[0].xyz, mr2)); - vec3 mvc1 = vec3(dot(cam._viewInverse[1].xyz, mr0), dot(cam._viewInverse[1].xyz, mr1), dot(cam._viewInverse[1].xyz, mr2)); - vec3 mvc2 = vec3(dot(cam._viewInverse[2].xyz, mr0), dot(cam._viewInverse[2].xyz, mr1), dot(cam._viewInverse[2].xyz, mr2)); - - _normal = vec3(dot(mvc0, inNormal.xyz), dot(mvc1, inNormal.xyz), dot(mvc2, inNormal.xyz)); - } - -} -)SCRIBE"; - -#endif diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 1a95d08902..9d2a3327d7 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -35,9 +35,8 @@ #include #include -#include "gputest_shaders.h" -#include "gputest_simple_frag.h" -#include "gputest_simple_vert.h" +#include "../../libraries/render-utils/simple_frag.h" +#include "../../libraries/render-utils/simple_vert.h" class RateCounter { std::vector times; @@ -200,8 +199,8 @@ BasicModelPointer makeCube () { gpu::BufferView normalView { normalBuffer, normalElement }; // Create shaders - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(basicVertexShader())); - auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(basicFragmentShader())); + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex({ simple_vert })); + auto fs = gpu::ShaderPointer(gpu::Shader::createPixel({ simple_frag })); auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); gpu::Shader::BindingSet bindings; diff --git a/tests/gpu-test/src/simple.slf b/tests/gpu-test/src/simple.slf deleted file mode 100644 index 31d33a73e4..0000000000 --- a/tests/gpu-test/src/simple.slf +++ /dev/null @@ -1,29 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// simple.frag -// fragment shader -// -// Created by Andrzej Kapolka on 9/15/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include DeferredBufferWrite.slh@> -<@include model/Material.slh@> - -// the interpolated normal -in vec3 _normal; -in vec3 _color; - -void main(void) { - Material material = getMaterial(); - packDeferredFragment( - normalize(_normal.xyz), - glowIntensity, - _color.rgb, - DEFAULT_SPECULAR, DEFAULT_SHININESS); -} diff --git a/tests/gpu-test/src/simple.slv b/tests/gpu-test/src/simple.slv deleted file mode 100644 index 99f404eaec..0000000000 --- a/tests/gpu-test/src/simple.slv +++ /dev/null @@ -1,36 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// simple.vert -// vertex shader -// -// Created by Andrzej Kapolka on 9/15/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include gpu/Inputs.slh@> - -<@include gpu/Transform.slh@> - -<$declareStandardTransform()$> - -// the interpolated normal - -out vec3 _normal; -out vec3 _color; -out vec2 _texCoord0; - -void main(void) { - _color = inColor.rgb; - _texCoord0 = inTexCoord0.st; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> -} \ No newline at end of file From bd18951be81cc33b608f0c4255639f42b066a19d Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Mon, 24 Aug 2015 13:13:24 -0700 Subject: [PATCH 154/170] fixed AUTOSCRIBE_SHADER_LIB to work for targets outside of libraries/ --- cmake/macros/SetupHifiProject.cmake | 2 +- tests/gpu-test/CMakeLists.txt | 2 +- tests/gpu-test/src/main.cpp | 4 ++-- tests/gpu-test/src/simple.slf | 29 +++++++++++++++++++++++ tests/gpu-test/src/simple.slv | 36 +++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 tests/gpu-test/src/simple.slf create mode 100644 tests/gpu-test/src/simple.slv diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index 1ed0d8a202..18a3a73588 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -23,7 +23,7 @@ macro(SETUP_HIFI_PROJECT) endforeach() # add the executable, include additional optional sources - add_executable(${TARGET_NAME} ${TARGET_SRCS} "${AUTOMTC_SRC}") + add_executable(${TARGET_NAME} ${TARGET_SRCS} "${AUTOMTC_SRC}" "${AUTOSCRIBE_SHADER_LIB_SRC}") set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN}) list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core) diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 19a41df776..abdbfc07f9 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME gpu-test) -AUTOSCRIBE_SHADER_LIB(gpu model render gpu-test) +AUTOSCRIBE_SHADER_LIB(gpu model render-utils) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL Script Widgets) diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 9d2a3327d7..758d9b29bb 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -35,8 +35,8 @@ #include #include -#include "../../libraries/render-utils/simple_frag.h" -#include "../../libraries/render-utils/simple_vert.h" +#include "simple_frag.h" +#include "simple_vert.h" class RateCounter { std::vector times; diff --git a/tests/gpu-test/src/simple.slf b/tests/gpu-test/src/simple.slf new file mode 100644 index 0000000000..31d33a73e4 --- /dev/null +++ b/tests/gpu-test/src/simple.slf @@ -0,0 +1,29 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/15/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include DeferredBufferWrite.slh@> +<@include model/Material.slh@> + +// the interpolated normal +in vec3 _normal; +in vec3 _color; + +void main(void) { + Material material = getMaterial(); + packDeferredFragment( + normalize(_normal.xyz), + glowIntensity, + _color.rgb, + DEFAULT_SPECULAR, DEFAULT_SHININESS); +} diff --git a/tests/gpu-test/src/simple.slv b/tests/gpu-test/src/simple.slv new file mode 100644 index 0000000000..99f404eaec --- /dev/null +++ b/tests/gpu-test/src/simple.slv @@ -0,0 +1,36 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple.vert +// vertex shader +// +// Created by Andrzej Kapolka on 9/15/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +// the interpolated normal + +out vec3 _normal; +out vec3 _color; +out vec2 _texCoord0; + +void main(void) { + _color = inColor.rgb; + _texCoord0 = inTexCoord0.st; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> +} \ No newline at end of file From 6549e47dbc948c49d5eac129dd50a7663bfeafb5 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 24 Aug 2015 14:35:19 -0700 Subject: [PATCH 155/170] fix crash in Controller when asking for device name out of bounds --- .../input-plugins/src/input-plugins/UserInputMapper.cpp | 8 ++++++++ .../input-plugins/src/input-plugins/UserInputMapper.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp index f6973cb31e..6c79ced280 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp @@ -32,6 +32,14 @@ UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Inpu } } +QString UserInputMapper::getDeviceName(uint16 deviceID) { + if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { + return _registeredDevices[deviceID]->_name; + } + return QString("unknown"); +} + + void UserInputMapper::resetAllDeviceBindings() { for (auto device : _registeredDevices) { device.second->resetDeviceBindings(); diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.h b/libraries/input-plugins/src/input-plugins/UserInputMapper.h index fca19af3d7..7aa775141a 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.h +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.h @@ -123,7 +123,7 @@ public: uint16 getFreeDeviceID() { return _nextFreeDeviceID++; } bool registerDevice(uint16 deviceID, const DeviceProxy::Pointer& device); DeviceProxy::Pointer getDeviceProxy(const Input& input); - QString getDeviceName(uint16 deviceID) { return _registeredDevices[deviceID]->_name; } + QString getDeviceName(uint16 deviceID); QVector getAvailableInputs(uint16 deviceID) { return _registeredDevices[deviceID]->getAvailabeInputs(); } void resetAllDeviceBindings(); void resetDevice(uint16 deviceID); From 380b0cb23cb20e219d90873842ae807c6e54df4d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 24 Aug 2015 15:24:10 -0700 Subject: [PATCH 156/170] Removing tabs from our javascript and C++ --- examples/FlockOfbirds.js | 64 +- examples/acScripts/ControlACs.js | 94 +- examples/acScripts/botProceduralWayPoints.js | 126 +- examples/acScripts/bot_procedural.js | 6 +- examples/blocks.js | 144 +-- examples/clap.js | 130 +-- examples/controllers/hydra/airGuitar.js | 138 +-- examples/controllers/hydra/drumStick.js | 72 +- examples/controllers/hydra/paddleBall.js | 164 +-- examples/controllers/hydra/squeezeHands.js | 6 +- examples/controllers/hydra/toyball.js | 2 +- examples/controllers/squeezeHands.js | 6 +- examples/cubePerfTest.js | 40 +- examples/entityScripts/playSoundOnClick.js | 2 +- examples/entityScripts/teleportOnClick.js | 2 +- examples/example/audio/birdSongs.js | 186 +-- examples/example/entities/butterflies.js | 12 +- .../example/entities/entityModelExample.js | 2 +- .../jsstreamplayerdomain-zone-entity.js | 30 +- examples/example/games/billiards.js | 136 +-- examples/example/games/hitEffect.js | 10 +- .../example/games/spaceInvadersExample.js | 2 +- .../scripts/controllerScriptingExamples.js | 2 +- .../example/ui/textInputOverlayExample.js | 62 +- examples/harmonicOscillator.js | 20 +- examples/hmdControls.js | 6 +- examples/hotPlaces.js | 10 +- examples/html/colpick.js | 1016 ++++++++--------- examples/libraries/entityPropertyDialogBox.js | 2 +- examples/mouseLook.js | 20 +- examples/planets.js | 128 +-- examples/popcorn.js | 232 ++-- examples/sit.js | 158 +-- .../diagnostics/typedArraysUnitTest.js | 4 +- .../zones/jsstreamplayerdomain-zone-entity.js | 30 +- examples/zones/jsstreamplayerdomain-zone.js | 4 +- ice-server/src/IceServer.cpp | 2 +- interface/src/avatar/SkeletonModel.cpp | 6 +- libraries/audio-client/src/AudioClient.h | 4 +- libraries/audio/src/AudioSourceNoise.h | 10 +- libraries/avatars/src/HeadData.h | 4 +- .../oculus/OculusLegacyDisplayPlugin.cpp | 2 +- .../openvr/OpenVrDisplayPlugin.cpp | 8 +- libraries/gpu/src/gpu/GLBackendShader.cpp | 110 +- libraries/gpu/src/gpu/GLBackendTexture.cpp | 2 +- libraries/model/src/model/Geometry.cpp | 12 +- libraries/octree/src/Octree.cpp | 2 +- libraries/octree/src/Octree.h | 4 +- libraries/octree/src/Plane.h | 30 +- libraries/render-utils/src/GlWindow.cpp | 4 +- libraries/script-engine/src/HFActionEvent.cpp | 2 +- libraries/shared/src/AACube.cpp | 8 +- libraries/shared/src/BufferParser.h | 2 +- libraries/shared/src/NumericalConstants.h | 2 +- libraries/shared/src/ThreadHelpers.h | 6 +- libraries/shared/src/windowshacks.h | 6 +- tools/vhacd-util/src/VHACDUtil.h | 2 +- 57 files changed, 1648 insertions(+), 1648 deletions(-) diff --git a/examples/FlockOfbirds.js b/examples/FlockOfbirds.js index 8b5088a268..dab086eff4 100644 --- a/examples/FlockOfbirds.js +++ b/examples/FlockOfbirds.js @@ -55,7 +55,7 @@ var birds = []; var playing = []; function randomVector(scale) { - return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; + return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; } function updateBirds(deltaTime) { @@ -217,40 +217,40 @@ function loadBirds(howMany) { var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw"]; /* Here are more sounds/species you can use , "mexicanWhipoorwill.raw", - "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", - "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", - "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", - "housewren.wav","hummingbird.wav", "mountainchickadee.wav", "nightjar.wav", "piebilledgrieb.wav", "pygmynuthatch.wav", - "whistlingduck.wav", "woodpecker.wav"]; + "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", + "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", + "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", + "housewren.wav","hummingbird.wav", "mountainchickadee.wav", "nightjar.wav", "piebilledgrieb.wav", "pygmynuthatch.wav", + "whistlingduck.wav", "woodpecker.wav"]; */ var colors = [ - { red: 242, green: 207, blue: 013 }, - { red: 238, green: 94, blue: 11 }, - { red: 81, green: 30, blue: 7 }, - { red: 195, green: 176, blue: 81 }, - { red: 235, green: 190, blue: 152 }, - { red: 167, green: 99, blue: 52 }, - { red: 199, green: 122, blue: 108 }, - { red: 246, green: 220, blue: 189 }, - { red: 208, green: 145, blue: 65 }, - { red: 173, green: 120 , blue: 71 }, - { red: 132, green: 147, blue: 174 }, - { red: 164, green: 74, blue: 40 }, - { red: 131, green: 127, blue: 134 }, - { red: 209, green: 157, blue: 117 }, - { red: 205, green: 191, blue: 193 }, - { red: 193, green: 154, blue: 118 }, - { red: 205, green: 190, blue: 169 }, - { red: 199, green: 111, blue: 69 }, - { red: 221, green: 223, blue: 228 }, - { red: 115, green: 92, blue: 87 }, - { red: 214, green: 165, blue: 137 }, - { red: 160, green: 124, blue: 33 }, - { red: 117, green: 91, blue: 86 }, - { red: 113, green: 104, blue: 107 }, - { red: 216, green: 153, blue: 99 }, - { red: 242, green: 226, blue: 64 } + { red: 242, green: 207, blue: 013 }, + { red: 238, green: 94, blue: 11 }, + { red: 81, green: 30, blue: 7 }, + { red: 195, green: 176, blue: 81 }, + { red: 235, green: 190, blue: 152 }, + { red: 167, green: 99, blue: 52 }, + { red: 199, green: 122, blue: 108 }, + { red: 246, green: 220, blue: 189 }, + { red: 208, green: 145, blue: 65 }, + { red: 173, green: 120 , blue: 71 }, + { red: 132, green: 147, blue: 174 }, + { red: 164, green: 74, blue: 40 }, + { red: 131, green: 127, blue: 134 }, + { red: 209, green: 157, blue: 117 }, + { red: 205, green: 191, blue: 193 }, + { red: 193, green: 154, blue: 118 }, + { red: 205, green: 190, blue: 169 }, + { red: 199, green: 111, blue: 69 }, + { red: 221, green: 223, blue: 228 }, + { red: 115, green: 92, blue: 87 }, + { red: 214, green: 165, blue: 137 }, + { red: 160, green: 124, blue: 33 }, + { red: 117, green: 91, blue: 86 }, + { red: 113, green: 104, blue: 107 }, + { red: 216, green: 153, blue: 99 }, + { red: 242, green: 226, blue: 64 } ]; var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; diff --git a/examples/acScripts/ControlACs.js b/examples/acScripts/ControlACs.js index 4f27a36b9d..45c498dc7f 100644 --- a/examples/acScripts/ControlACs.js +++ b/examples/acScripts/ControlACs.js @@ -57,18 +57,18 @@ setupToolBars(); function setupToolBars() { - if (toolBars.length > 0) { - print("Multiple calls to Recorder.js:setupToolBars()"); - return; - } + if (toolBars.length > 0) { + print("Multiple calls to Recorder.js:setupToolBars()"); + return; + } Tool.IMAGE_HEIGHT /= 2; Tool.IMAGE_WIDTH /= 2; - for (i = 0; i <= NUM_AC; i++) { - toolBars.push(new ToolBar(0, 0, ToolBar.HORIZONTAL)); - toolBars[i].setBack((i === NUM_AC) ? COLOR_MASTER : COLOR_TOOL_BAR, ALPHA_OFF); + for (i = 0; i <= NUM_AC; i++) { + toolBars.push(new ToolBar(0, 0, ToolBar.HORIZONTAL)); + toolBars[i].setBack((i === NUM_AC) ? COLOR_MASTER : COLOR_TOOL_BAR, ALPHA_OFF); - onOffIcon.push(toolBars[i].addTool({ + onOffIcon.push(toolBars[i].addTool({ imageURL: TOOL_ICON_URL + "ac-on-off.svg", subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, x: 0, y: 0, @@ -78,7 +78,7 @@ function setupToolBars() { visible: true }, true, true)); - playIcon[i] = toolBars[i].addTool({ + playIcon[i] = toolBars[i].addTool({ imageURL: TOOL_ICON_URL + "play.svg", subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, width: Tool.IMAGE_WIDTH, @@ -88,7 +88,7 @@ function setupToolBars() { }, false); var playLoopWidthFactor = 1.65; - playLoopIcon[i] = toolBars[i].addTool({ + playLoopIcon[i] = toolBars[i].addTool({ imageURL: TOOL_ICON_URL + "play-and-loop.svg", subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, @@ -97,7 +97,7 @@ function setupToolBars() { visible: true }, false); - stopIcon[i] = toolBars[i].addTool({ + stopIcon[i] = toolBars[i].addTool({ imageURL: TOOL_ICON_URL + "recording-stop.svg", width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT, @@ -105,7 +105,7 @@ function setupToolBars() { visible: true }, false); - nameOverlays.push(Overlays.addOverlay("text", { + nameOverlays.push(Overlays.addOverlay("text", { backgroundColor: { red: 0, green: 0, blue: 0 }, font: { size: TEXT_HEIGHT }, text: (i === NUM_AC) ? "Master" : i + ". " + @@ -120,30 +120,30 @@ function setupToolBars() { backgroundAlpha: ALPHA_OFF, visible: true })); - } + } } function sendCommand(id, action) { - if (action === SHOW) { - toolBars[id].selectTool(onOffIcon[id], false); + if (action === SHOW) { + toolBars[id].selectTool(onOffIcon[id], false); toolBars[id].setAlpha(ALPHA_ON, playIcon[id]); toolBars[id].setAlpha(ALPHA_ON, playLoopIcon[id]); toolBars[id].setAlpha(ALPHA_ON, stopIcon[id]); - } else if (action === HIDE) { - toolBars[id].selectTool(onOffIcon[id], true); + } else if (action === HIDE) { + toolBars[id].selectTool(onOffIcon[id], true); toolBars[id].setAlpha(ALPHA_OFF, playIcon[id]); toolBars[id].setAlpha(ALPHA_OFF, playLoopIcon[id]); toolBars[id].setAlpha(ALPHA_OFF, stopIcon[id]); - } else if (toolBars[id].toolSelected(onOffIcon[id])) { + } else if (toolBars[id].toolSelected(onOffIcon[id])) { + return; + } + + if (id === toolBars.length - 1) { + for (i = 0; i < NUM_AC; i++) { + sendCommand(i, action); + } return; } - - if (id === toolBars.length - 1) { - for (i = 0; i < NUM_AC; i++) { - sendCommand(i, action); - } - return; - } // TODO: Fix this to use some mechanism other than voxels //Voxels.setVoxel(controlVoxelPosition.x + id * controlVoxelSize, controlVoxelPosition.y, controlVoxelPosition.z, @@ -151,11 +151,11 @@ function sendCommand(id, action) { } function mousePressEvent(event) { - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - // Check master control - var i = toolBars.length - 1; - if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + // Check master control + var i = toolBars.length - 1; + if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { if (toolBars[i].toolSelected(onOffIcon[i])) { sendCommand(i, SHOW); } else { @@ -169,8 +169,8 @@ function mousePressEvent(event) { sendCommand(i, STOP); } else { // Check individual controls - for (i = 0; i < NUM_AC; i++) { - if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + for (i = 0; i < NUM_AC; i++) { + if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { if (toolBars[i].toolSelected(onOffIcon[i], false)) { sendCommand(i, SHOW); } else { @@ -185,40 +185,40 @@ function mousePressEvent(event) { } else { } - } + } } } function moveUI() { var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; - var relative = { x: 70, y: 75 + (NUM_AC) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) }; + var relative = { x: 70, y: 75 + (NUM_AC) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) }; - for (i = 0; i <= NUM_AC; i++) { - toolBars[i].move(relative.x, + for (i = 0; i <= NUM_AC; i++) { + toolBars[i].move(relative.x, windowDimensions.y - relative.y + i * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize)); - Overlays.editOverlay(nameOverlays[i], { + Overlays.editOverlay(nameOverlays[i], { x: toolBars[i].x - ToolBar.SPACING, y: toolBars[i].y - textSize }); - } + } } function update() { - var newDimensions = Controller.getViewportDimensions(); - if (windowDimensions.x != newDimensions.x || - windowDimensions.y != newDimensions.y) { - windowDimensions = newDimensions; - moveUI(); - } + var newDimensions = Controller.getViewportDimensions(); + if (windowDimensions.x != newDimensions.x || + windowDimensions.y != newDimensions.y) { + windowDimensions = newDimensions; + moveUI(); + } } function scriptEnding() { - for (i = 0; i <= NUM_AC; i++) { - toolBars[i].cleanup(); - Overlays.deleteOverlay(nameOverlays[i]); - } + for (i = 0; i <= NUM_AC; i++) { + toolBars[i].cleanup(); + Overlays.deleteOverlay(nameOverlays[i]); + } } Controller.mousePressEvent.connect(mousePressEvent); diff --git a/examples/acScripts/botProceduralWayPoints.js b/examples/acScripts/botProceduralWayPoints.js index 1e00c9f8b8..fbe8b03c3d 100644 --- a/examples/acScripts/botProceduralWayPoints.js +++ b/examples/acScripts/botProceduralWayPoints.js @@ -42,26 +42,26 @@ function printVector(string, vector) { var AVATAR_PELVIS_HEIGHT = 0.84;//only change this if you have an odd size avatar var wayPoints = []; //input locations for all the waypoints - wayPoints[0] = {x:8131.5, y:202.0, z:8261.5}; //input the location of the start position - wayPoints[1] = {x: 8160.5, y: 202.0, z: 8261.5}; //input the location of the first way point - wayPoints[2] = {x: 8160.5, y: 203.0, z: 8270.5}; - wayPoints[3] = {x: 8142.5, y: 204.0, z: 8270.5}; - wayPoints[4] = {x: 8142.5, y: 204.0, z: 8272.5}; - wayPoints[5] = {x: 8160.5, y: 203.0, z: 8272.5}; - wayPoints[6] = {x: 8160.5, y: 202.0, z: 8284.5}; - wayPoints[7] = {x: 8111.5, y: 202.0, z: 8284.5};// continue to add locations and add or remove lines as needed. - + wayPoints[0] = {x:8131.5, y:202.0, z:8261.5}; //input the location of the start position + wayPoints[1] = {x: 8160.5, y: 202.0, z: 8261.5}; //input the location of the first way point + wayPoints[2] = {x: 8160.5, y: 203.0, z: 8270.5}; + wayPoints[3] = {x: 8142.5, y: 204.0, z: 8270.5}; + wayPoints[4] = {x: 8142.5, y: 204.0, z: 8272.5}; + wayPoints[5] = {x: 8160.5, y: 203.0, z: 8272.5}; + wayPoints[6] = {x: 8160.5, y: 202.0, z: 8284.5}; + wayPoints[7] = {x: 8111.5, y: 202.0, z: 8284.5};// continue to add locations and add or remove lines as needed. + var pauseTimes = []; // the number of pauseTimes must equal the number of wayPoints. Time is in milliseconds - pauseTimes[0] = 5000; //waiting to go to wayPoint0 (startPoint) - pauseTimes[1] = 10000; //waiting to go to wayPoint1 - pauseTimes[2] = 3000; - pauseTimes[3] = 3000; - pauseTimes[4] = 8000; - pauseTimes[5] = 6000; - pauseTimes[6] = 3000; - pauseTimes[7] = 3000;// add or delete to match way points. + pauseTimes[0] = 5000; //waiting to go to wayPoint0 (startPoint) + pauseTimes[1] = 10000; //waiting to go to wayPoint1 + pauseTimes[2] = 3000; + pauseTimes[3] = 3000; + pauseTimes[4] = 8000; + pauseTimes[5] = 6000; + pauseTimes[6] = 3000; + pauseTimes[7] = 3000;// add or delete to match way points. - + wayPoints[0].y = wayPoints[0].y + AVATAR_PELVIS_HEIGHT; wayPoints[1].y = wayPoints[1].y + AVATAR_PELVIS_HEIGHT; wayPoints[2].y = wayPoints[2].y + AVATAR_PELVIS_HEIGHT; @@ -173,10 +173,10 @@ function playRandomSound() { function playRandomFootstepSound() { var whichSound = Math.floor((Math.random() * footstepSounds.length)); - Audio.playSound(footstepSounds[whichSound], { - position: Avatar.position, + Audio.playSound(footstepSounds[whichSound], { + position: Avatar.position, volume: 1.0 - }); + }); } // Facial Animation @@ -620,59 +620,59 @@ function stopWalking() { var pauseTimer; - function pause(checkPoint, rotation, delay){ + function pause(checkPoint, rotation, delay){ - pauseTimer = Script.setTimeout(function() { - targetPosition = checkPoint; - targetOrientation = rotation; - - isMoving = true; - }, delay); + pauseTimer = Script.setTimeout(function() { + targetPosition = checkPoint; + targetOrientation = rotation; + + isMoving = true; + }, delay); - - } + + } function handleWalking(deltaTime) { - - if (!isMoving){ - if(targetPosition.x == 0){targetPosition = wayPoints[1]; isMoving = true;} //Start by heading for wayPoint1 - - else{ - for (var j = 0; j <= wayPoints.length; j++) { - if (targetPosition == wayPoints[j]) { + + if (!isMoving){ + if(targetPosition.x == 0){targetPosition = wayPoints[1]; isMoving = true;} //Start by heading for wayPoint1 + + else{ + for (var j = 0; j <= wayPoints.length; j++) { + if (targetPosition == wayPoints[j]) { - if(j == wayPoints.length -1){ j= -1;} - var k = j + 1; - var toTarget = Vec3.normalize(Vec3.subtract(wayPoints[k], Avatar.position)); - var localVector = Vec3.multiplyQbyV(Avatar.orientation, { x: 0, y: 0, z: -1 }); - toTarget.y = 0; // I recommend doing that if you don't want weird rotation to occur that are not around Y. + if(j == wayPoints.length -1){ j= -1;} + var k = j + 1; + var toTarget = Vec3.normalize(Vec3.subtract(wayPoints[k], Avatar.position)); + var localVector = Vec3.multiplyQbyV(Avatar.orientation, { x: 0, y: 0, z: -1 }); + toTarget.y = 0; // I recommend doing that if you don't want weird rotation to occur that are not around Y. - var axis = Vec3.normalize(Vec3.cross(toTarget, localVector)); - var angle = Math.acos(Vec3.dot(toTarget, localVector)) * 180 / Math.PI; + var axis = Vec3.normalize(Vec3.cross(toTarget, localVector)); + var angle = Math.acos(Vec3.dot(toTarget, localVector)) * 180 / Math.PI; - if (Vec3.dot(Vec3.cross(axis, localVector), toTarget) < 0) { - angle = -angle; - } - var delta = 1; - - var quat = Quat.angleAxis(angle, axis); - Avatar.orientation = Quat.multiply(quat, Avatar.orientation); - + if (Vec3.dot(Vec3.cross(axis, localVector), toTarget) < 0) { + angle = -angle; + } + var delta = 1; + + var quat = Quat.angleAxis(angle, axis); + Avatar.orientation = Quat.multiply(quat, Avatar.orientation); + - pause(wayPoints[k], Avatar.orientation, pauseTimes[k]); + pause(wayPoints[k], Avatar.orientation, pauseTimes[k]); - - break; - } - } - } - } + + break; + } + } + } + } - else - if (isMoving) { - + else + if (isMoving) { + var targetVector = Vec3.subtract(targetPosition, Avatar.position); var distance = Vec3.length(targetVector); if (distance <= avatarVelocity * deltaTime) { @@ -693,7 +693,7 @@ function handleWalking(deltaTime) { if (avatarVelocity > avatarMaxVelocity) avatarVelocity = avatarMaxVelocity; } Avatar.position = Vec3.sum(Avatar.position, Vec3.multiply(direction, avatarVelocity * deltaTime)); - + wasMovingLastFrame = true; diff --git a/examples/acScripts/bot_procedural.js b/examples/acScripts/bot_procedural.js index 8386662dba..18d42a0e60 100644 --- a/examples/acScripts/bot_procedural.js +++ b/examples/acScripts/bot_procedural.js @@ -135,10 +135,10 @@ function playRandomSound() { function playRandomFootstepSound() { var whichSound = Math.floor((Math.random() * footstepSounds.length)); - Audio.playSound(footstepSounds[whichSound], { - position: Avatar.position, + Audio.playSound(footstepSounds[whichSound], { + position: Avatar.position, volume: 1.0 - }); + }); } // ************************************ Facial Animation ********************************** diff --git a/examples/blocks.js b/examples/blocks.js index 7bc52824db..29ccd9fff0 100644 --- a/examples/blocks.js +++ b/examples/blocks.js @@ -31,94 +31,94 @@ blockTypes.push({ x: 1, y: 1, z: 5, red: 0, green: 255, blue: 255 }); var center = Vec3.sum(MyAvatar.position, Vec3.multiply(FLOOR_SIZE * 2.0, Quat.getFront(Camera.getOrientation()))); var floor = Entities.addEntity( - { type: "Box", - position: Vec3.subtract(center, { x: 0, y: SCALE / 2.0, z: 0 }), - dimensions: { x: FLOOR_SIZE, y: FLOOR_THICKNESS, z: FLOOR_SIZE }, - color: { red: 128, green: 128, blue: 128 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - locked: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.subtract(center, { x: 0, y: SCALE / 2.0, z: 0 }), + dimensions: { x: FLOOR_SIZE, y: FLOOR_THICKNESS, z: FLOOR_SIZE }, + color: { red: 128, green: 128, blue: 128 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + locked: true, + lifetime: LIFETIME }); var edge1 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), - dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS }, - color: { red: 100, green: 100, blue: 100 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: true, - locked: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), + dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: true, + locked: true, + lifetime: LIFETIME }); var edge2 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: -FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), - dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS }, - color: { red: 100, green: 100, blue: 100 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: true, - locked: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: -FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), + dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: true, + locked: true, + lifetime: LIFETIME }); var edge3 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: -FLOOR_SIZE / 2.0 }), - dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS }, - color: { red: 100, green: 100, blue: 100 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: true, - locked: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: -FLOOR_SIZE / 2.0 }), + dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: true, + locked: true, + lifetime: LIFETIME }); var edge4 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: FLOOR_SIZE / 2.0 }), - dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS }, - color: { red: 100, green: 100, blue: 100 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: true, - locked: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: FLOOR_SIZE / 2.0 }), + dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: true, + locked: true, + lifetime: LIFETIME }); blocks = []; for (var i = 0; i < NUM_BLOCKS; i++) { - var which = Math.floor(Math.random() * blockTypes.length); - var type = blockTypes[which]; - blocks.push(Entities.addEntity( - { type: "Box", - position: { x: center.x + (Math.random() - 0.5) * (FLOOR_SIZE * 0.75), - y: center.y + DROP_HEIGHT, - z: center.z + (Math.random() - 0.5) * (FLOOR_SIZE * 0.75) }, - dimensions: { x: type.x * SCALE, y: type.y * SCALE, z: type.z * SCALE }, - color: { red: type.red, green: type.green, blue: type.blue }, - gravity: { x: 0, y: GRAVITY, z: 0 }, - velocity: { x: 0, y: 0.05, z: 0 }, - ignoreCollisions: false, - damping: DAMPING, - lifetime: LIFETIME, - collisionsWillMove: true })); + var which = Math.floor(Math.random() * blockTypes.length); + var type = blockTypes[which]; + blocks.push(Entities.addEntity( + { type: "Box", + position: { x: center.x + (Math.random() - 0.5) * (FLOOR_SIZE * 0.75), + y: center.y + DROP_HEIGHT, + z: center.z + (Math.random() - 0.5) * (FLOOR_SIZE * 0.75) }, + dimensions: { x: type.x * SCALE, y: type.y * SCALE, z: type.z * SCALE }, + color: { red: type.red, green: type.green, blue: type.blue }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, + ignoreCollisions: false, + damping: DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true })); } function scriptEnding() { - Entities.editEntity(edge1, { locked: false }); - Entities.editEntity(edge2, { locked: false }); - Entities.editEntity(edge3, { locked: false }); - Entities.editEntity(edge4, { locked: false }); - Entities.editEntity(floor, { locked: false }); - Entities.deleteEntity(edge1); - Entities.deleteEntity(edge2); - Entities.deleteEntity(edge3); - Entities.deleteEntity(edge4); - Entities.deleteEntity(floor); + Entities.editEntity(edge1, { locked: false }); + Entities.editEntity(edge2, { locked: false }); + Entities.editEntity(edge3, { locked: false }); + Entities.editEntity(edge4, { locked: false }); + Entities.editEntity(floor, { locked: false }); + Entities.deleteEntity(edge1); + Entities.deleteEntity(edge2); + Entities.deleteEntity(edge3); + Entities.deleteEntity(edge4); + Entities.deleteEntity(floor); - for (var i = 0; i < NUM_BLOCKS; i++) { - Entities.deleteEntity(blocks[i]); - } + for (var i = 0; i < NUM_BLOCKS; i++) { + Entities.deleteEntity(blocks[i]); + } } Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/examples/clap.js b/examples/clap.js index 3b333e4345..4ec740e3f8 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -50,98 +50,98 @@ var clapRate = CLAP_START_RATE; var startedTimer = false; function maybePlaySound(deltaTime) { - // Set the location and other info for the sound to play + // Set the location and other info for the sound to play - var animationDetails = MyAvatar.getAnimationDetails(clapAnimation); + var animationDetails = MyAvatar.getAnimationDetails(clapAnimation); - var frame = Math.floor(animationDetails.frameIndex); + var frame = Math.floor(animationDetails.frameIndex); - if (frame != lastAnimFrame) { - lastAnimFrame = frame; - } + if (frame != lastAnimFrame) { + lastAnimFrame = frame; + } - for (var i = 0; i < startEndFrames.length; i++) { - if (frame == startEndFrames[i].start && (frame != lastClapFrame)) { - playClap(1.0, Camera.getPosition()); - lastClapFrame = frame; - } - } + for (var i = 0; i < startEndFrames.length; i++) { + if (frame == startEndFrames[i].start && (frame != lastClapFrame)) { + playClap(1.0, Camera.getPosition()); + lastClapFrame = frame; + } + } - var palm1Position = MyAvatar.getLeftPalmPosition(); - var palm2Position = MyAvatar.getRightPalmPosition(); - var distanceBetween = Vec3.length(Vec3.subtract(palm1Position, palm2Position)); + var palm1Position = MyAvatar.getLeftPalmPosition(); + var palm2Position = MyAvatar.getRightPalmPosition(); + var distanceBetween = Vec3.length(Vec3.subtract(palm1Position, palm2Position)); - var palm1Velocity = Controller.getSpatialControlVelocity(1); - var palm2Velocity = Controller.getSpatialControlVelocity(3); - var closingVelocity = Vec3.length(Vec3.subtract(palm1Velocity, palm2Velocity)); + var palm1Velocity = Controller.getSpatialControlVelocity(1); + var palm2Velocity = Controller.getSpatialControlVelocity(3); + var closingVelocity = Vec3.length(Vec3.subtract(palm1Velocity, palm2Velocity)); - const CLAP_SPEED = 0.7; - const CLAP_DISTANCE = 0.15; + const CLAP_SPEED = 0.7; + const CLAP_DISTANCE = 0.15; - if ((closingVelocity > CLAP_SPEED) && (distanceBetween < CLAP_DISTANCE) && !clappingNow) { - var volume = closingVelocity / 2.0; - if (volume > 1.0) volume = 1.0; - playClap(volume, palm1Position); - clappingNow = true; - } else if (clappingNow && (distanceBetween > CLAP_DISTANCE * 1.2)) { - clappingNow = false; - } + if ((closingVelocity > CLAP_SPEED) && (distanceBetween < CLAP_DISTANCE) && !clappingNow) { + var volume = closingVelocity / 2.0; + if (volume > 1.0) volume = 1.0; + playClap(volume, palm1Position); + clappingNow = true; + } else if (clappingNow && (distanceBetween > CLAP_DISTANCE * 1.2)) { + clappingNow = false; + } } function playClap(volume, position) { - var clip = Math.floor(Math.random() * numberOfSounds); - Audio.playSound(claps[clip], { - position: position, + var clip = Math.floor(Math.random() * numberOfSounds); + Audio.playSound(claps[clip], { + position: position, volume: volume - }); + }); } var FASTEST_CLAP_INTERVAL = 150.0; var SLOWEST_CLAP_INTERVAL = 750.0; Controller.keyPressEvent.connect(function(event) { - if(event.text == "SHIFT") { - if (!clickClappingNow) { - clickClappingNow = true; - clickStartTime = new Date(); - lastClapFrame = 0; - } else { - // start or adjust clapping speed based on the duration between clicks - clickEndTime = new Date(); - var milliseconds = Math.max(clickEndTime - clickStartTime, FASTEST_CLAP_INTERVAL); - clickStartTime = new Date(); - if (milliseconds < SLOWEST_CLAP_INTERVAL) { - clapRate = ANIMATION_FRAMES_PER_CLAP * (1000.0 / milliseconds); - playClap(1.0, Camera.getPosition()); - MyAvatar.stopAnimation(clapAnimation); - MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); - } - collectedClicks = collectedClicks + 1; - } - } + if(event.text == "SHIFT") { + if (!clickClappingNow) { + clickClappingNow = true; + clickStartTime = new Date(); + lastClapFrame = 0; + } else { + // start or adjust clapping speed based on the duration between clicks + clickEndTime = new Date(); + var milliseconds = Math.max(clickEndTime - clickStartTime, FASTEST_CLAP_INTERVAL); + clickStartTime = new Date(); + if (milliseconds < SLOWEST_CLAP_INTERVAL) { + clapRate = ANIMATION_FRAMES_PER_CLAP * (1000.0 / milliseconds); + playClap(1.0, Camera.getPosition()); + MyAvatar.stopAnimation(clapAnimation); + MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); + } + collectedClicks = collectedClicks + 1; + } + } }); var CLAP_END_WAIT_MSECS = 300; Controller.keyReleaseEvent.connect(function(event) { if (event.text == "SHIFT") { - collectedClicks = 0; - if (!startedTimer) { - collectedClicks = 0; - Script.setTimeout(stopClapping, CLAP_END_WAIT_MSECS); - startedTimer = true; - } + collectedClicks = 0; + if (!startedTimer) { + collectedClicks = 0; + Script.setTimeout(stopClapping, CLAP_END_WAIT_MSECS); + startedTimer = true; + } } }); function stopClapping() { - if (collectedClicks == 0) { - startedTimer = false; - MyAvatar.stopAnimation(clapAnimation); - clapRate = CLAP_START_RATE; - clickClappingNow = false; - } else { - startedTimer = false; - } + if (collectedClicks == 0) { + startedTimer = false; + MyAvatar.stopAnimation(clapAnimation); + clapRate = CLAP_START_RATE; + clickClappingNow = false; + } else { + startedTimer = false; + } } // Connect a call back that happens every frame diff --git a/examples/controllers/hydra/airGuitar.js b/examples/controllers/hydra/airGuitar.js index 750bfb5bc7..7fbbfc48bd 100644 --- a/examples/controllers/hydra/airGuitar.js +++ b/examples/controllers/hydra/airGuitar.js @@ -18,13 +18,13 @@ function length(v) { function printVector(v) { - print(v.x + ", " + v.y + ", " + v.z); - return; + print(v.x + ", " + v.y + ", " + v.z); + return; } function vMinus(a, b) { - var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; - return rval; + var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; + return rval; } // The model file to be used for the guitar @@ -58,11 +58,11 @@ var whichChord = 1; var leftHanded = true; if (leftHanded) { - var strumHand = 0; - var chordHand = 1; + var strumHand = 0; + var chordHand = 1; } else { - var strumHand = 1; - var chordHand = 0; + var strumHand = 1; + var chordHand = 0; } var lastPosition = { x: 0.0, @@ -76,77 +76,77 @@ var position; MyAvatar.attach(guitarModel, "Hips", {x: -0.2, y: 0.0, z: 0.1}, Quat.fromPitchYawRollDegrees(90, 00, 90), 1.0); function checkHands(deltaTime) { - for (var palm = 0; palm < 2; palm++) { - var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); - var volume = length(palmVelocity) / 5.0; - var position = Controller.getSpatialControlPosition(palm * 2 + 1); - var myPelvis = MyAvatar.position; - var trigger = Controller.getTriggerValue(strumHand); - var chord = Controller.getTriggerValue(chordHand); + for (var palm = 0; palm < 2; palm++) { + var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); + var volume = length(palmVelocity) / 5.0; + var position = Controller.getSpatialControlPosition(palm * 2 + 1); + var myPelvis = MyAvatar.position; + var trigger = Controller.getTriggerValue(strumHand); + var chord = Controller.getTriggerValue(chordHand); - if (volume > 1.0) volume = 1.0; - if ((chord > 0.1) && soundPlaying.isPlaying) { - // If chord finger trigger pulled, stop current chord - print("stopped sound"); - soundPlaying.stop(); - } + if (volume > 1.0) volume = 1.0; + if ((chord > 0.1) && soundPlaying.isPlaying) { + // If chord finger trigger pulled, stop current chord + print("stopped sound"); + soundPlaying.stop(); + } - var BUTTON_COUNT = 6; + var BUTTON_COUNT = 6; - // Change guitars if button FWD (5) pressed - if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 5)) { - if (!selectorPressed) { - guitarSelector += NUM_CHORDS; - if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) { - guitarSelector = 0; - } - selectorPressed = true; - } - } else { - selectorPressed = false; - } + // Change guitars if button FWD (5) pressed + if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 5)) { + if (!selectorPressed) { + guitarSelector += NUM_CHORDS; + if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) { + guitarSelector = 0; + } + selectorPressed = true; + } + } else { + selectorPressed = false; + } - if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 1)) { - whichChord = 1; - } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 2)) { - whichChord = 2; - } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 3)) { - whichChord = 3; - } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 4)) { - whichChord = 4; - } + if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 1)) { + whichChord = 1; + } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 2)) { + whichChord = 2; + } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 3)) { + whichChord = 3; + } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 4)) { + whichChord = 4; + } - if (palm == strumHand) { + if (palm == strumHand) { - var STRUM_HEIGHT_ABOVE_PELVIS = 0.10; - var strumTriggerHeight = myPelvis.y + STRUM_HEIGHT_ABOVE_PELVIS; - //printVector(position); - if ( ( ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) || - ((position.y > strumTriggerHeight) && (lastPosition.y <= strumTriggerHeight)) ) && (trigger > 0.1) ){ - // If hand passes downward or upward through 'strings', and finger trigger pulled, play - playChord(position, volume); - } - lastPosition = Controller.getSpatialControlPosition(palm * 2 + 1); - } - } + var STRUM_HEIGHT_ABOVE_PELVIS = 0.10; + var strumTriggerHeight = myPelvis.y + STRUM_HEIGHT_ABOVE_PELVIS; + //printVector(position); + if ( ( ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) || + ((position.y > strumTriggerHeight) && (lastPosition.y <= strumTriggerHeight)) ) && (trigger > 0.1) ){ + // If hand passes downward or upward through 'strings', and finger trigger pulled, play + playChord(position, volume); + } + lastPosition = Controller.getSpatialControlPosition(palm * 2 + 1); + } + } } function playChord(position, volume) { - if (soundPlaying.isPlaying) { - print("stopped sound"); - soundPlaying.stop(); - } + if (soundPlaying.isPlaying) { + print("stopped sound"); + soundPlaying.stop(); + } - print("Played sound: " + whichChord + " at volume " + options.volume); + print("Played sound: " + whichChord + " at volume " + options.volume); if (!soundPlaying) { - soundPlaying = Audio.playSound(chords[guitarSelector + whichChord], { - position: position, + soundPlaying = Audio.playSound(chords[guitarSelector + whichChord], { + position: position, volume: volume - }); + }); } else { soundPlaying.restart(); } - + } function keyPressEvent(event) { @@ -156,14 +156,14 @@ function keyPressEvent(event) { whichChord = 1; playChord(MyAvatar.position, keyVolume); } else if (event.text == "2") { - whichChord = 2; - playChord(MyAvatar.position, keyVolume); + whichChord = 2; + playChord(MyAvatar.position, keyVolume); } else if (event.text == "3") { - whichChord = 3; - playChord(MyAvatar.position, keyVolume); + whichChord = 3; + playChord(MyAvatar.position, keyVolume); } else if (event.text == "4") { whichChord = 4; - playChord(MyAvatar.position, keyVolume); + playChord(MyAvatar.position, keyVolume); } } diff --git a/examples/controllers/hydra/drumStick.js b/examples/controllers/hydra/drumStick.js index d2a948b98e..e59528aa80 100644 --- a/examples/controllers/hydra/drumStick.js +++ b/examples/controllers/hydra/drumStick.js @@ -18,12 +18,12 @@ function length(v) { function printVector(v) { - print(v.x + ", " + v.y + ", " + v.z + "\n"); + print(v.x + ", " + v.y + ", " + v.z + "\n"); } function vMinus(a, b) { - var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; - return rval; + var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; + return rval; } // First, load two percussion sounds to be used on the sticks @@ -42,41 +42,41 @@ strokeSpeed[0] = 0.0; strokeSpeed[1] = 0.0; function checkSticks(deltaTime) { - for (var palm = 0; palm < 2; palm++) { - var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); - var speed = length(palmVelocity); - - const TRIGGER_SPEED = 0.30; // Lower this value to let you 'drum' more gently - const STOP_SPEED = 0.01; // Speed below which a sound will trigger - const GAIN = 0.5; // Loudness compared to stick velocity - const AVERAGING = 0.2; // How far back to sample trailing velocity - - // Measure trailing average stroke speed to ultimately set volume - strokeSpeed[palm] = (1.0 - AVERAGING) * strokeSpeed[palm] + AVERAGING * (speed * GAIN); - - if (state[palm] == 0) { - // Waiting for downward speed to indicate stroke - if ((palmVelocity.y < 0.0) && (strokeSpeed[palm] > TRIGGER_SPEED)) { - state[palm] = 1; - } - } else if (state[palm] == 1) { - // Waiting for change in velocity direction or slowing to trigger drum sound - if ((palmVelocity.y > 0.0) || (speed < STOP_SPEED)) { - state[palm] = 0; + for (var palm = 0; palm < 2; palm++) { + var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); + var speed = length(palmVelocity); - var options = { position: Controller.getSpatialControlPosition(palm * 2 + 1) }; - - if (strokeSpeed[palm] > 1.0) { strokeSpeed[palm] = 1.0; } - options.volume = strokeSpeed[palm]; + const TRIGGER_SPEED = 0.30; // Lower this value to let you 'drum' more gently + const STOP_SPEED = 0.01; // Speed below which a sound will trigger + const GAIN = 0.5; // Loudness compared to stick velocity + const AVERAGING = 0.2; // How far back to sample trailing velocity - if (palm == 0) { - Audio.playSound(drum1, options); - } else { - Audio.playSound(drum2, options); - } - } - } - } + // Measure trailing average stroke speed to ultimately set volume + strokeSpeed[palm] = (1.0 - AVERAGING) * strokeSpeed[palm] + AVERAGING * (speed * GAIN); + + if (state[palm] == 0) { + // Waiting for downward speed to indicate stroke + if ((palmVelocity.y < 0.0) && (strokeSpeed[palm] > TRIGGER_SPEED)) { + state[palm] = 1; + } + } else if (state[palm] == 1) { + // Waiting for change in velocity direction or slowing to trigger drum sound + if ((palmVelocity.y > 0.0) || (speed < STOP_SPEED)) { + state[palm] = 0; + + var options = { position: Controller.getSpatialControlPosition(palm * 2 + 1) }; + + if (strokeSpeed[palm] > 1.0) { strokeSpeed[palm] = 1.0; } + options.volume = strokeSpeed[palm]; + + if (palm == 0) { + Audio.playSound(drum1, options); + } else { + Audio.playSound(drum2, options); + } + } + } + } } // Connect a call back that happens every frame diff --git a/examples/controllers/hydra/paddleBall.js b/examples/controllers/hydra/paddleBall.js index ef4fe92544..13c6e2eb62 100644 --- a/examples/controllers/hydra/paddleBall.js +++ b/examples/controllers/hydra/paddleBall.js @@ -61,48 +61,48 @@ var offButton = Overlays.addOverlay("image", { var ball, paddle, paddleModel, line; function createEntities() { - ball = Entities.addEntity( - { type: "Sphere", - position: Controller.getSpatialControlPosition(controllerID), - dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE }, - color: BALL_COLOR, - gravity: { x: 0, y: GRAVITY, z: 0 }, - ignoreCollisions: false, - damping: 0.50, - collisionsWillMove: true }); + ball = Entities.addEntity( + { type: "Sphere", + position: Controller.getSpatialControlPosition(controllerID), + dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE }, + color: BALL_COLOR, + gravity: { x: 0, y: GRAVITY, z: 0 }, + ignoreCollisions: false, + damping: 0.50, + collisionsWillMove: true }); - paddle = Entities.addEntity( - { type: "Box", - position: Controller.getSpatialControlPosition(controllerID), - dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 0.80 }, - color: PADDLE_COLOR, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - damping: 0.10, - visible: false, - rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), - collisionsWillMove: false }); + paddle = Entities.addEntity( + { type: "Box", + position: Controller.getSpatialControlPosition(controllerID), + dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 0.80 }, + color: PADDLE_COLOR, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + damping: 0.10, + visible: false, + rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), + collisionsWillMove: false }); - modelURL = "http://public.highfidelity.io/models/attachments/pong_paddle.fbx"; - paddleModel = Entities.addEntity( - { type: "Model", - position: Vec3.sum(Controller.getSpatialControlPosition(controllerID), PADDLE_BOX_OFFSET), - dimensions: { x: PADDLE_SIZE * 1.5, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 1.25 }, - color: PADDLE_COLOR, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: true, - modelURL: modelURL, - damping: 0.10, - rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), - collisionsWillMove: false }); + modelURL = "http://public.highfidelity.io/models/attachments/pong_paddle.fbx"; + paddleModel = Entities.addEntity( + { type: "Model", + position: Vec3.sum(Controller.getSpatialControlPosition(controllerID), PADDLE_BOX_OFFSET), + dimensions: { x: PADDLE_SIZE * 1.5, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 1.25 }, + color: PADDLE_COLOR, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: true, + modelURL: modelURL, + damping: 0.10, + rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), + collisionsWillMove: false }); - line = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: LINE_COLOR, - alpha: 1, - visible: true, - lineWidth: 2 }); + line = Overlays.addOverlay("line3d", { + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: LINE_COLOR, + alpha: 1, + visible: true, + lineWidth: 2 }); MyAvatar.stopAnimation(leftHandAnimation); MyAvatar.stopAnimation(rightHandAnimation); @@ -110,7 +110,7 @@ function createEntities() { } function deleteEntities() { - Entities.deleteEntity(ball); + Entities.deleteEntity(ball); Entities.deleteEntity(paddle); Entities.deleteEntity(paddleModel); Overlays.deleteOverlay(line); @@ -118,54 +118,54 @@ function deleteEntities() { } function update(deltaTime) { - var palmPosition = Controller.getSpatialControlPosition(controllerID); - var controllerActive = (Vec3.length(palmPosition) > 0); + var palmPosition = Controller.getSpatialControlPosition(controllerID); + var controllerActive = (Vec3.length(palmPosition) > 0); - if (!gameOn && controllerActive) { - createEntities(); - gameOn = true; - } else if (gameOn && !controllerActive) { - deleteEntities(); - gameOn = false; - } - if (!gameOn || !controllerActive) { - return; - } + if (!gameOn && controllerActive) { + createEntities(); + gameOn = true; + } else if (gameOn && !controllerActive) { + deleteEntities(); + gameOn = false; + } + if (!gameOn || !controllerActive) { + return; + } var paddleOrientation = leftHanded ? PADDLE_ORIENTATION : Quat.multiply(PADDLE_ORIENTATION, Quat.fromPitchYawRollDegrees(0, 180, 0)); - var paddleWorldOrientation = Quat.multiply(Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), paddleOrientation); - var holdPosition = Vec3.sum(leftHanded ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), - Vec3.multiplyQbyV(paddleWorldOrientation, leftHanded ? HOLD_POSITION_LEFT_OFFSET : HOLD_POSITION_RIGHT_OFFSET )); + var paddleWorldOrientation = Quat.multiply(Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), paddleOrientation); + var holdPosition = Vec3.sum(leftHanded ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), + Vec3.multiplyQbyV(paddleWorldOrientation, leftHanded ? HOLD_POSITION_LEFT_OFFSET : HOLD_POSITION_RIGHT_OFFSET )); - var props = Entities.getEntityProperties(ball); - var spring = Vec3.subtract(holdPosition, props.position); - var springLength = Vec3.length(spring); + var props = Entities.getEntityProperties(ball); + var spring = Vec3.subtract(holdPosition, props.position); + var springLength = Vec3.length(spring); - spring = Vec3.normalize(spring); - var ballVelocity = Vec3.sum(props.velocity, Vec3.multiply(springLength * SPRING_FORCE * deltaTime, spring)); - Entities.editEntity(ball, { velocity: ballVelocity }); - Overlays.editOverlay(line, { start: props.position, end: holdPosition }); - Entities.editEntity(paddle, { position: holdPosition, - velocity: Controller.getSpatialControlVelocity(controllerID), - rotation: paddleWorldOrientation }); - Entities.editEntity(paddleModel, { position: Vec3.sum(holdPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_BOX_OFFSET)), - velocity: Controller.getSpatialControlVelocity(controllerID), - rotation: paddleWorldOrientation }); + spring = Vec3.normalize(spring); + var ballVelocity = Vec3.sum(props.velocity, Vec3.multiply(springLength * SPRING_FORCE * deltaTime, spring)); + Entities.editEntity(ball, { velocity: ballVelocity }); + Overlays.editOverlay(line, { start: props.position, end: holdPosition }); + Entities.editEntity(paddle, { position: holdPosition, + velocity: Controller.getSpatialControlVelocity(controllerID), + rotation: paddleWorldOrientation }); + Entities.editEntity(paddleModel, { position: Vec3.sum(holdPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_BOX_OFFSET)), + velocity: Controller.getSpatialControlVelocity(controllerID), + rotation: paddleWorldOrientation }); } function entityCollisionWithEntity(entity1, entity2, collision) { - if ((entity1.id == ball.id) || (entity2.id ==ball.id)) { - var props1 = Entities.getEntityProperties(entity1); - var props2 = Entities.getEntityProperties(entity2); - var dVel = Vec3.length(Vec3.subtract(props1.velocity, props2.velocity)); - var currentTime = new Date().getTime(); - var MIN_MSECS_BETWEEN_BOUNCE_SOUNDS = 100; - var MIN_VELOCITY_FOR_SOUND_IMPACT = 0.25; - if ((dVel > MIN_VELOCITY_FOR_SOUND_IMPACT) && (currentTime - lastSoundTime) > MIN_MSECS_BETWEEN_BOUNCE_SOUNDS) { - Audio.playSound(hitSound, { position: props1.position, volume: Math.min(dVel, 1.0) }); - lastSoundTime = new Date().getTime(); - } + if ((entity1.id == ball.id) || (entity2.id ==ball.id)) { + var props1 = Entities.getEntityProperties(entity1); + var props2 = Entities.getEntityProperties(entity2); + var dVel = Vec3.length(Vec3.subtract(props1.velocity, props2.velocity)); + var currentTime = new Date().getTime(); + var MIN_MSECS_BETWEEN_BOUNCE_SOUNDS = 100; + var MIN_VELOCITY_FOR_SOUND_IMPACT = 0.25; + if ((dVel > MIN_VELOCITY_FOR_SOUND_IMPACT) && (currentTime - lastSoundTime) > MIN_MSECS_BETWEEN_BOUNCE_SOUNDS) { + Audio.playSound(hitSound, { position: props1.position, volume: Math.min(dVel, 1.0) }); + lastSoundTime = new Date().getTime(); + } } } @@ -189,9 +189,9 @@ function menuItemEvent(menuItem) { } function scriptEnding() { - if (gameOn) { - deleteEntities(); - } + if (gameOn) { + deleteEntities(); + } Overlays.deleteOverlay(offButton); MyAvatar.stopAnimation(leftHandAnimation); MyAvatar.stopAnimation(rightHandAnimation); diff --git a/examples/controllers/hydra/squeezeHands.js b/examples/controllers/hydra/squeezeHands.js index b1e9274905..00ba4800f4 100644 --- a/examples/controllers/hydra/squeezeHands.js +++ b/examples/controllers/hydra/squeezeHands.js @@ -63,7 +63,7 @@ Script.update.connect(function(deltaTime) { MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); } if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { - MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); + MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); } lastLeftFrame = leftFrame; @@ -71,6 +71,6 @@ Script.update.connect(function(deltaTime) { }); Script.scriptEnding.connect(function() { - MyAvatar.stopAnimation(leftHandAnimation); - MyAvatar.stopAnimation(rightHandAnimation); + MyAvatar.stopAnimation(leftHandAnimation); + MyAvatar.stopAnimation(rightHandAnimation); }); \ No newline at end of file diff --git a/examples/controllers/hydra/toyball.js b/examples/controllers/hydra/toyball.js index 18a4341ca6..0f5db9b2c0 100644 --- a/examples/controllers/hydra/toyball.js +++ b/examples/controllers/hydra/toyball.js @@ -136,7 +136,7 @@ function checkControllerSide(whichSide) { }; Entities.editEntity(closestEntity, properties); - Audio.playSound(catchSound, { position: ballPosition }); + Audio.playSound(catchSound, { position: ballPosition }); return; // exit early } diff --git a/examples/controllers/squeezeHands.js b/examples/controllers/squeezeHands.js index 69f18f50a1..9c42b35d43 100644 --- a/examples/controllers/squeezeHands.js +++ b/examples/controllers/squeezeHands.js @@ -66,7 +66,7 @@ Script.update.connect(function(deltaTime) { MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); } if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { - MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); + MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); } lastLeftFrame = leftFrame; @@ -74,6 +74,6 @@ Script.update.connect(function(deltaTime) { }); Script.scriptEnding.connect(function() { - MyAvatar.stopAnimation(leftHandAnimation); - MyAvatar.stopAnimation(rightHandAnimation); + MyAvatar.stopAnimation(leftHandAnimation); + MyAvatar.stopAnimation(rightHandAnimation); }); \ No newline at end of file diff --git a/examples/cubePerfTest.js b/examples/cubePerfTest.js index e349b7add7..bdf123ae33 100644 --- a/examples/cubePerfTest.js +++ b/examples/cubePerfTest.js @@ -21,32 +21,32 @@ for (var i = 0; i < ids.length; i++) { var id = ids[i]; var properties = Entities.getEntityProperties(id); if (properties.name == "PerfTest") { - Entities.deleteEntity(id); + Entities.deleteEntity(id); } } // Create initial test particles that will move according to gravity from the planets for (var x = 0; x < SIDE_SIZE; x++) { - for (var y = 0; y < SIDE_SIZE; y++) { - for (var z = 0; z < SIDE_SIZE; z++) { - var gray = Math.random() * 155; - var cube = Math.random() > 0.5; - var color = { red: 100 + gray, green: 100 + gray, blue: 100 + gray }; - var position = { x: 512 + x * 0.2, y: 512 + y * 0.2, z: 512 + z * 0.2}; - var radius = Math.random() * 0.1; - boxes.push(Entities.addEntity({ - type: cube ? "Box" : "Sphere", - name: "PerfTest", - position: position, - dimensions: { x: radius, y: radius, z: radius }, - color: color, - ignoreCollisions: true, - collisionsWillMove: false, - lifetime: LIFETIME - })); - } - } + for (var y = 0; y < SIDE_SIZE; y++) { + for (var z = 0; z < SIDE_SIZE; z++) { + var gray = Math.random() * 155; + var cube = Math.random() > 0.5; + var color = { red: 100 + gray, green: 100 + gray, blue: 100 + gray }; + var position = { x: 512 + x * 0.2, y: 512 + y * 0.2, z: 512 + z * 0.2}; + var radius = Math.random() * 0.1; + boxes.push(Entities.addEntity({ + type: cube ? "Box" : "Sphere", + name: "PerfTest", + position: position, + dimensions: { x: radius, y: radius, z: radius }, + color: color, + ignoreCollisions: true, + collisionsWillMove: false, + lifetime: LIFETIME + })); + } + } } diff --git a/examples/entityScripts/playSoundOnClick.js b/examples/entityScripts/playSoundOnClick.js index de1e57e062..402eb5c8b2 100644 --- a/examples/entityScripts/playSoundOnClick.js +++ b/examples/entityScripts/playSoundOnClick.js @@ -24,6 +24,6 @@ Audio.playSound(bird, { position: MyAvatar.position, volume: 0.5 - }); + }); }; }) diff --git a/examples/entityScripts/teleportOnClick.js b/examples/entityScripts/teleportOnClick.js index 11677b12d5..c1d5470877 100644 --- a/examples/entityScripts/teleportOnClick.js +++ b/examples/entityScripts/teleportOnClick.js @@ -13,6 +13,6 @@ // (function(){ this.clickDownOnEntity = function(entityID, mouseEvent) { - MyAvatar.position = Entities.getEntityProperties(entityID).position; + MyAvatar.position = Entities.getEntityProperties(entityID).position; }; }) diff --git a/examples/example/audio/birdSongs.js b/examples/example/audio/birdSongs.js index 876f942dbc..557fc81f5b 100644 --- a/examples/example/audio/birdSongs.js +++ b/examples/example/audio/birdSongs.js @@ -25,24 +25,24 @@ var BIRD_MASTER_VOLUME = 0.5; var useLights = true; function randomVector(scale) { - return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; + return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; } function maybePlaySound(deltaTime) { - if (Math.random() < RATE) { - // Set the location and other info for the sound to play - var whichBird = Math.floor(Math.random() * birds.length); - //print("playing sound # " + whichBird); - var position = { + if (Math.random() < RATE) { + // Set the location and other info for the sound to play + var whichBird = Math.floor(Math.random() * birds.length); + //print("playing sound # " + whichBird); + var position = { x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), - y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), + y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) }; - var options = { - position: position, + var options = { + position: position, volume: BIRD_MASTER_VOLUME - }; - var entityId = Entities.addEntity({ + }; + var entityId = Entities.addEntity({ type: "Sphere", position: position, dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }, @@ -50,59 +50,59 @@ function maybePlaySound(deltaTime) { lifetime: 10 }); - if (useLights) { - var lightId = Entities.addEntity({ - type: "Light", - position: position, - dimensions: { x: LIGHT_RADIUS, y: LIGHT_RADIUS, z: LIGHT_RADIUS }, + if (useLights) { + var lightId = Entities.addEntity({ + type: "Light", + position: position, + dimensions: { x: LIGHT_RADIUS, y: LIGHT_RADIUS, z: LIGHT_RADIUS }, - isSpotlight: false, - diffuseColor: birds[whichBird].color, - ambientColor: { red: 0, green: 0, blue: 0 }, - specularColor: { red: 255, green: 255, blue: 255 }, + isSpotlight: false, + diffuseColor: birds[whichBird].color, + ambientColor: { red: 0, green: 0, blue: 0 }, + specularColor: { red: 255, green: 255, blue: 255 }, - constantAttenuation: 0, - linearAttenuation: 4.0, - quadraticAttenuation: 2.0, - lifetime: 10 - }); - } + constantAttenuation: 0, + linearAttenuation: 4.0, + quadraticAttenuation: 2.0, + lifetime: 10 + }); + } - playing.push({ audioId: Audio.playSound(birds[whichBird].sound, options), entityId: entityId, lightId: lightId, color: birds[whichBird].color }); - } - if (playing.length != numPlaying) { - numPlaying = playing.length; - //print("number playing = " + numPlaying); - } - for (var i = 0; i < playing.length; i++) { - if (!playing[i].audioId.isPlaying) { - Entities.deleteEntity(playing[i].entityId); - if (useLights) { - Entities.deleteEntity(playing[i].lightId); - } - playing.splice(i, 1); - } else { - var loudness = playing[i].audioId.loudness; - var newColor = { red: playing[i].color.red, green: playing[i].color.green, blue: playing[i].color.blue }; - if (loudness > 0.05) { - newColor.red *= (1.0 - loudness); - newColor.green *= (1.0 - loudness); - newColor.blue *= (1.0 - loudness); - } - var properties = Entities.getEntityProperties(playing[i].entityId); - var newPosition = Vec3.sum(properties.position, randomVector(BIRD_VELOCITY * deltaTime)); - if (properties) { - properties.position = newPosition; - Entities.editEntity(playing[i].entityId, { position: properties.position, color: newColor }); - } - if (useLights) { - var lightProperties = Entities.getEntityProperties(playing[i].lightId); - if (lightProperties) { - Entities.editEntity(playing[i].lightId, { position: newPosition, diffuseColor: newColor }); - } - } - } - } + playing.push({ audioId: Audio.playSound(birds[whichBird].sound, options), entityId: entityId, lightId: lightId, color: birds[whichBird].color }); + } + if (playing.length != numPlaying) { + numPlaying = playing.length; + //print("number playing = " + numPlaying); + } + for (var i = 0; i < playing.length; i++) { + if (!playing[i].audioId.isPlaying) { + Entities.deleteEntity(playing[i].entityId); + if (useLights) { + Entities.deleteEntity(playing[i].lightId); + } + playing.splice(i, 1); + } else { + var loudness = playing[i].audioId.loudness; + var newColor = { red: playing[i].color.red, green: playing[i].color.green, blue: playing[i].color.blue }; + if (loudness > 0.05) { + newColor.red *= (1.0 - loudness); + newColor.green *= (1.0 - loudness); + newColor.blue *= (1.0 - loudness); + } + var properties = Entities.getEntityProperties(playing[i].entityId); + var newPosition = Vec3.sum(properties.position, randomVector(BIRD_VELOCITY * deltaTime)); + if (properties) { + properties.position = newPosition; + Entities.editEntity(playing[i].entityId, { position: properties.position, color: newColor }); + } + if (useLights) { + var lightProperties = Entities.getEntityProperties(playing[i].lightId); + if (lightProperties) { + Entities.editEntity(playing[i].lightId, { position: newPosition, diffuseColor: newColor }); + } + } + } + } } loadBirds(); @@ -114,46 +114,46 @@ Script.scriptEnding.connect(function() { for (var i = 0; i < playing.length; i++) { Entities.deleteEntity(playing[i].entityId); if (useLights) { - Entities.deleteEntity(playing[i].lightId); + Entities.deleteEntity(playing[i].lightId); } } }); function loadBirds() { var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw", "mexicanWhipoorwill.raw", - "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", - "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", - "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", - "housewren.wav","hummingbird.wav", "mountainchickadee.wav", "nightjar.wav", "piebilledgrieb.wav", "pygmynuthatch.wav", - "whistlingduck.wav", "woodpecker.wav"]; + "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", + "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", + "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", + "housewren.wav","hummingbird.wav", "mountainchickadee.wav", "nightjar.wav", "piebilledgrieb.wav", "pygmynuthatch.wav", + "whistlingduck.wav", "woodpecker.wav"]; var colors = [ - { red: 242, green: 207, blue: 013 }, - { red: 238, green: 94, blue: 11 }, - { red: 81, green: 30, blue: 7 }, - { red: 195, green: 176, blue: 81 }, - { red: 235, green: 190, blue: 152 }, - { red: 167, green: 99, blue: 52 }, - { red: 199, green: 122, blue: 108 }, - { red: 246, green: 220, blue: 189 }, - { red: 208, green: 145, blue: 65 }, - { red: 173, green: 120 , blue: 71 }, - { red: 132, green: 147, blue: 174 }, - { red: 164, green: 74, blue: 40 }, - { red: 131, green: 127, blue: 134 }, - { red: 209, green: 157, blue: 117 }, - { red: 205, green: 191, blue: 193 }, - { red: 193, green: 154, blue: 118 }, - { red: 205, green: 190, blue: 169 }, - { red: 199, green: 111, blue: 69 }, - { red: 221, green: 223, blue: 228 }, - { red: 115, green: 92, blue: 87 }, - { red: 214, green: 165, blue: 137 }, - { red: 160, green: 124, blue: 33 }, - { red: 117, green: 91, blue: 86 }, - { red: 113, green: 104, blue: 107 }, - { red: 216, green: 153, blue: 99 }, - { red: 242, green: 226, blue: 64 } + { red: 242, green: 207, blue: 013 }, + { red: 238, green: 94, blue: 11 }, + { red: 81, green: 30, blue: 7 }, + { red: 195, green: 176, blue: 81 }, + { red: 235, green: 190, blue: 152 }, + { red: 167, green: 99, blue: 52 }, + { red: 199, green: 122, blue: 108 }, + { red: 246, green: 220, blue: 189 }, + { red: 208, green: 145, blue: 65 }, + { red: 173, green: 120 , blue: 71 }, + { red: 132, green: 147, blue: 174 }, + { red: 164, green: 74, blue: 40 }, + { red: 131, green: 127, blue: 134 }, + { red: 209, green: 157, blue: 117 }, + { red: 205, green: 191, blue: 193 }, + { red: 193, green: 154, blue: 118 }, + { red: 205, green: 190, blue: 169 }, + { red: 199, green: 111, blue: 69 }, + { red: 221, green: 223, blue: 228 }, + { red: 115, green: 92, blue: 87 }, + { red: 214, green: 165, blue: 137 }, + { red: 160, green: 124, blue: 33 }, + { red: 117, green: 91, blue: 86 }, + { red: 113, green: 104, blue: 107 }, + { red: 216, green: 153, blue: 99 }, + { red: 242, green: 226, blue: 64 } ]; var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; diff --git a/examples/example/entities/butterflies.js b/examples/example/entities/butterflies.js index 5ad11c46e9..8b02cf5768 100644 --- a/examples/example/entities/butterflies.js +++ b/examples/example/entities/butterflies.js @@ -50,7 +50,7 @@ if (!FIXED_LOCATION) { var flockPosition = { x: 4999.6, y: 4986.5, z: 5003.5 }; } - + // This is our butterfly object function defineButterfly(entityID, targetPosition) { this.entityID = entityID; @@ -72,7 +72,7 @@ function addButterfly() { size = MINSIZE + Math.random() * RANGESIZE; var dimensions = Vec3.multiply(NATURAL_SIZE_OF_BUTTERFLY, (size / maxSize)); - + var GRAVITY = -0.2; var newFrameRate = 20 + Math.random() * 30; var properties = { @@ -82,12 +82,12 @@ function addButterfly() { rotation: Quat.fromPitchYawRollDegrees(-80 + Math.random() * 20, Math.random() * 360.0, 0.0), velocity: { x: 0, y: 0, z: 0 }, gravity: { x: 0, y: GRAVITY, z: 0 }, - damping: 0.00001, - dimensions: dimensions, + damping: 0.00001, + dimensions: dimensions, color: color, - animationURL: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx", + animationURL: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx", animationSettings: "{\"firstFrame\":0,\"fps\":" + newFrameRate + ",\"frameIndex\":0,\"hold\":false,\"lastFrame\":10000,\"loop\":true,\"running\":true,\"startAutomatically\":false}", - modelURL: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx" + modelURL: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx" }; butterflies.push(Entities.addEntity(properties)); } diff --git a/examples/example/entities/entityModelExample.js b/examples/example/entities/entityModelExample.js index aab2324496..35f29b6a62 100644 --- a/examples/example/entities/entityModelExample.js +++ b/examples/example/entities/entityModelExample.js @@ -25,7 +25,7 @@ var modelPropertiesA = { x: 2.16, y: 3.34, z: 0.54 - }, + }, modelURL: HIFI_PUBLIC_BUCKET + "meshes/Feisar_Ship.FBX", lifetime: 20 }; diff --git a/examples/example/entities/jsstreamplayerdomain-zone-entity.js b/examples/example/entities/jsstreamplayerdomain-zone-entity.js index 9a8cb8b8b4..3730c71b4f 100644 --- a/examples/example/entities/jsstreamplayerdomain-zone-entity.js +++ b/examples/example/entities/jsstreamplayerdomain-zone-entity.js @@ -15,19 +15,19 @@ // Function which exists inside of an entity which triggers as a user approches it. (function() { - const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js"; - function isScriptRunning(script) { - script = script.toLowerCase().trim(); - var runningScripts = ScriptDiscoveryService.getRunning(); - for (i in runningScripts) { - if (runningScripts[i].url.toLowerCase().trim() == script) { - return true; - } - } - return false; - }; - - if (!isScriptRunning(SCRIPT_NAME)) { - Script.load(SCRIPT_NAME); - } + const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js"; + function isScriptRunning(script) { + script = script.toLowerCase().trim(); + var runningScripts = ScriptDiscoveryService.getRunning(); + for (i in runningScripts) { + if (runningScripts[i].url.toLowerCase().trim() == script) { + return true; + } + } + return false; + }; + + if (!isScriptRunning(SCRIPT_NAME)) { + Script.load(SCRIPT_NAME); + } }) \ No newline at end of file diff --git a/examples/example/games/billiards.js b/examples/example/games/billiards.js index 25ff5e7eae..c110b33042 100644 --- a/examples/example/games/billiards.js +++ b/examples/example/games/billiards.js @@ -51,56 +51,56 @@ var reticle = Overlays.addOverlay("image", { function makeTable(pos) { // Top tableParts.push(Entities.addEntity( - { type: "Box", + { type: "Box", position: pos, - dimensions: { x: LENGTH * SCALE, y: HEIGHT, z: WIDTH * SCALE }, - color: { red: 0, green: 255, blue: 0 } })); + dimensions: { x: LENGTH * SCALE, y: HEIGHT, z: WIDTH * SCALE }, + color: { red: 0, green: 255, blue: 0 } })); // Long Bumpers tableParts.push(Entities.addEntity( - { type: "Box", + { type: "Box", position: { x: pos.x - LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( - { type: "Box", + { type: "Box", position: { x: pos.x + LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( - { type: "Box", + { type: "Box", position: { x: pos.x - LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( - { type: "Box", + { type: "Box", position: { x: pos.x + LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); // End bumpers tableParts.push(Entities.addEntity( - { type: "Box", + { type: "Box", position: { x: pos.x + (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z }, - dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z }, + dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( - { type: "Box", + { type: "Box", position: { x: pos.x - (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z }, - dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z }, + dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); } @@ -110,54 +110,54 @@ function makeBalls(pos) { var ballNumber = 0; var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z }; for (var row = 1; row <= 5; row++) { - ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE); - for (var spot = 0; spot < row; spot++) { - balls.push(Entities.addEntity( - { type: "Model", + ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE); + for (var spot = 0; spot < row; spot++) { + balls.push(Entities.addEntity( + { type: "Model", modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/ball_" + whichBall[ballNumber].toString() + ".fbx", - position: ballPosition, - dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, + position: ballPosition, + dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, rotation: Quat.fromPitchYawRollDegrees((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20), - color: { red: 255, green: 255, blue: 255 }, - gravity: { x: 0, y: GRAVITY, z: 0 }, - velocity: {x: 0, y: -0.2, z: 0 }, - ignoreCollisions: false, - damping: 0.50, + color: { red: 255, green: 255, blue: 255 }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: {x: 0, y: -0.2, z: 0 }, + ignoreCollisions: false, + damping: 0.50, shapeType: "sphere", - collisionSoundURL: hitSound, - collisionsWillMove: true })); - ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; + collisionSoundURL: hitSound, + collisionsWillMove: true })); + ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; ballNumber++; - } - ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE; + } + ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE; } // Cue Ball cuePosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z }; cueBall = Entities.addEntity( - { type: "Model", + { type: "Model", modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/cue_ball.fbx", - position: cuePosition, - dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, - color: { red: 255, green: 255, blue: 255 }, - gravity: { x: 0, y: GRAVITY, z: 0 }, - angularVelocity: { x: 0, y: 0, z: 0 }, - velocity: {x: 0, y: -0.2, z: 0 }, - ignoreCollisions: false, - damping: 0.50, + position: cuePosition, + dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, + color: { red: 255, green: 255, blue: 255 }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + angularVelocity: { x: 0, y: 0, z: 0 }, + velocity: {x: 0, y: -0.2, z: 0 }, + ignoreCollisions: false, + damping: 0.50, shapeType: "sphere", - collisionsWillMove: true }); + collisionsWillMove: true }); } function isObjectBall(id) { for (var i; i < balls.length; i++) { - if (balls[i].id == id) { - return true; - } + if (balls[i].id == id) { + return true; + } } return false; } @@ -177,7 +177,7 @@ function shootCue(velocity) { { type: "Sphere", position: cuePosition, dimensions: { x: SHOOTER_SIZE, y: SHOOTER_SIZE, z: SHOOTER_SIZE }, - color: SHOOTER_COLOR, + color: SHOOTER_COLOR, velocity: velocity, lifetime: BULLET_LIFETIME, gravity: { x: 0, y: BULLET_GRAVITY, z: 0 }, @@ -191,8 +191,8 @@ function shootCue(velocity) { function keyReleaseEvent(event) { if ((startStroke > 0) && event.text == "SPACE") { - var endTime = new Date().getTime(); - var delta = endTime - startStroke; + var endTime = new Date().getTime(); + var delta = endTime - startStroke; shootCue(delta / 100.0); startStroke = 0; } @@ -201,16 +201,16 @@ function keyReleaseEvent(event) { function keyPressEvent(event) { // Fire a cue ball if ((startStroke == 0) && (event.text == "SPACE")) { - startStroke = new Date().getTime(); + startStroke = new Date().getTime(); } } function cleanup() { for (var i = 0; i < tableParts.length; i++) { - Entities.deleteEntity(tableParts[i]); + Entities.deleteEntity(tableParts[i]); } for (var i = 0; i < balls.length; i++) { - Entities.deleteEntity(balls[i]); + Entities.deleteEntity(balls[i]); } Overlays.deleteOverlay(reticle); Entities.deleteEntity(cueBall); diff --git a/examples/example/games/hitEffect.js b/examples/example/games/hitEffect.js index 0ba9ea14d1..181a7dcceb 100644 --- a/examples/example/games/hitEffect.js +++ b/examples/example/games/hitEffect.js @@ -15,11 +15,11 @@ var hitEffectEnabled = false; toggleHitEffect(); function toggleHitEffect() { - Script.setTimeout(function() { - hitEffectEnabled = !hitEffectEnabled; - Scene.setEngineDisplayHitEffect(hitEffectEnabled); - toggleHitEffect(); - }, 1000); + Script.setTimeout(function() { + hitEffectEnabled = !hitEffectEnabled; + Scene.setEngineDisplayHitEffect(hitEffectEnabled); + toggleHitEffect(); + }, 1000); } diff --git a/examples/example/games/spaceInvadersExample.js b/examples/example/games/spaceInvadersExample.js index 08ad56c04d..330351e009 100644 --- a/examples/example/games/spaceInvadersExample.js +++ b/examples/example/games/spaceInvadersExample.js @@ -346,7 +346,7 @@ function fireMissile() { color: { red: 0, green: 0, blue: 255 }, lifetime: 5 }); - Script.addEventHandler(myMissile, "collisionWithEntity", entityCollisionWithEntity); + Script.addEventHandler(myMissile, "collisionWithEntity", entityCollisionWithEntity); var options = {} if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, diff --git a/examples/example/scripts/controllerScriptingExamples.js b/examples/example/scripts/controllerScriptingExamples.js index 26a1999bbb..515effd15c 100644 --- a/examples/example/scripts/controllerScriptingExamples.js +++ b/examples/example/scripts/controllerScriptingExamples.js @@ -85,7 +85,7 @@ print(s.scale); // inputName: string representing the input var availableInputs = Controller.getAvailableInputs(1); for (i = 0; i < availableInputs.length; i++) { - print(availableInputs[i].inputName); + print(availableInputs[i].inputName); } // You can modify key bindings by using these avaiable inputs diff --git a/examples/example/ui/textInputOverlayExample.js b/examples/example/ui/textInputOverlayExample.js index d212a547cf..04be0b92e9 100644 --- a/examples/example/ui/textInputOverlayExample.js +++ b/examples/example/ui/textInputOverlayExample.js @@ -1,7 +1,7 @@ // // textInputOverlayExample.js -// Captures keystrokes and generates a string which is displayed as a text Overlay +// Captures keystrokes and generates a string which is displayed as a text Overlay // demonstrates keystroke events, mouse click events and overlay buttons. // Created by Adrian McCarlie 7 October 2014 // @@ -62,7 +62,7 @@ var button1 = Overlays.addOverlay("image", { // green button color: readyColor, visible: true }); -// This will create an image overlay of another button. +// This will create an image overlay of another button. var button2 = Overlays.addOverlay("image", { // red button x: buttonLocationX, y: locationY + 60, @@ -72,32 +72,32 @@ var button2 = Overlays.addOverlay("image", { // red button imageURL: "https://public.highfidelity.io/images/thumb.png", color: { red: 250, green: 2, blue: 2}, visible: true, -}); +}); // When our script shuts down, we should clean up all of our overlays function scriptEnding() { Overlays.deleteOverlay(inputWindow); Overlays.deleteOverlay(button1); - Overlays.deleteOverlay(button2); + Overlays.deleteOverlay(button2); //Return control of keys to default on script ending for(var i=0; i 0) { - cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); - fillRGBFields(col, cal.get(0)); - fillHSBFields(col, cal.get(0)); - } else if (this.parentNode.className.indexOf('_hsb') > 0) { - cal.data('colpick').color = col = fixHSB({ - h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), - s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), - b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) - }); - fillRGBFields(col, cal.get(0)); - fillHexFields(col, cal.get(0)); - } else { - cal.data('colpick').color = col = rgbToHsb(fixRGB({ - r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), - g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), - b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) - })); - fillHexFields(col, cal.get(0)); - fillHSBFields(col, cal.get(0)); - } - setSelector(col, cal.get(0)); - setHue(col, cal.get(0)); - setNewColor(col, cal.get(0)); - cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); - }, - //Change style on blur and on focus of inputs - blur = function (ev) { - $(this).parent().removeClass('colpick_focus'); - }, - focus = function () { - $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); - $(this).parent().addClass('colpick_focus'); - }, - //Increment/decrement arrows functions - downIncrement = function (ev) { - ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; - var field = $(this).parent().find('input').focus(); - var current = { - el: $(this).parent().addClass('colpick_slider'), - max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), - y: ev.pageY, - field: field, - val: parseInt(field.val(), 10), - preview: $(this).parent().parent().data('colpick').livePreview - }; - $(document).mouseup(current, upIncrement); - $(document).mousemove(current, moveIncrement); - }, - moveIncrement = function (ev) { - ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); - if (ev.data.preview) { - change.apply(ev.data.field.get(0), [true]); - } - return false; - }, - upIncrement = function (ev) { - change.apply(ev.data.field.get(0), [true]); - ev.data.el.removeClass('colpick_slider').find('input').focus(); - $(document).off('mouseup', upIncrement); - $(document).off('mousemove', moveIncrement); - return false; - }, - //Hue slider functions - downHue = function (ev) { - ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; - var current = { - cal: $(this).parent(), - y: $(this).offset().top - }; - $(document).on('mouseup touchend',current,upHue); - $(document).on('mousemove touchmove',current,moveHue); - - var pageY = ((ev.type == 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); - change.apply( - current.cal.data('colpick') - .fields.eq(4).val(parseInt(360*(current.cal.data('colpick').height - (pageY - current.y))/current.cal.data('colpick').height, 10)) - .get(0), - [current.cal.data('colpick').livePreview] - ); - return false; - }, - moveHue = function (ev) { - var pageY = ((ev.type == 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); - change.apply( - ev.data.cal.data('colpick') - .fields.eq(4).val(parseInt(360*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.y))))/ev.data.cal.data('colpick').height, 10)) - .get(0), - [ev.data.preview] - ); - return false; - }, - upHue = function (ev) { - fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); - fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); - $(document).off('mouseup touchend',upHue); - $(document).off('mousemove touchmove',moveHue); - return false; - }, - //Color selector functions - downSelector = function (ev) { - ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; - var current = { - cal: $(this).parent(), - pos: $(this).offset() - }; - current.preview = current.cal.data('colpick').livePreview; - - $(document).on('mouseup touchend',current,upSelector); - $(document).on('mousemove touchmove',current,moveSelector); + var colpick = function () { + var + tpl = '
#
R
G
B
H
S
B
', + defaults = { + showEvent: 'click', + onShow: function () {}, + onBeforeShow: function(){}, + onHide: function () {}, + onChange: function () {}, + onSubmit: function () {}, + colorScheme: 'light', + color: '3289c7', + livePreview: true, + flat: false, + layout: 'full', + submit: 1, + submitText: 'OK', + height: 156 + }, + //Fill the inputs of the plugin + fillRGBFields = function (hsb, cal) { + var rgb = hsbToRgb(hsb); + $(cal).data('colpick').fields + .eq(1).val(rgb.r).end() + .eq(2).val(rgb.g).end() + .eq(3).val(rgb.b).end(); + }, + fillHSBFields = function (hsb, cal) { + $(cal).data('colpick').fields + .eq(4).val(Math.round(hsb.h)).end() + .eq(5).val(Math.round(hsb.s)).end() + .eq(6).val(Math.round(hsb.b)).end(); + }, + fillHexFields = function (hsb, cal) { + $(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb)); + }, + //Set the round selector position + setSelector = function (hsb, cal) { + $(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100})); + $(cal).data('colpick').selectorIndic.css({ + left: parseInt($(cal).data('colpick').height * hsb.s/100, 10), + top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10) + }); + }, + //Set the hue selector position + setHue = function (hsb, cal) { + $(cal).data('colpick').hue.css('top', parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h/360, 10)); + }, + //Set current and new colors + setCurrentColor = function (hsb, cal) { + $(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + setNewColor = function (hsb, cal) { + $(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + //Called when the new color is changed + change = function (ev) { + var cal = $(this).parent().parent(), col; + if (this.parentNode.className.indexOf('_hex') > 0) { + cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } else if (this.parentNode.className.indexOf('_hsb') > 0) { + cal.data('colpick').color = col = fixHSB({ + h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), + s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) + }); + fillRGBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + } else { + cal.data('colpick').color = col = rgbToHsb(fixRGB({ + r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), + g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) + })); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } + setSelector(col, cal.get(0)); + setHue(col, cal.get(0)); + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); + }, + //Change style on blur and on focus of inputs + blur = function (ev) { + $(this).parent().removeClass('colpick_focus'); + }, + focus = function () { + $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); + $(this).parent().addClass('colpick_focus'); + }, + //Increment/decrement arrows functions + downIncrement = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var field = $(this).parent().find('input').focus(); + var current = { + el: $(this).parent().addClass('colpick_slider'), + max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), + y: ev.pageY, + field: field, + val: parseInt(field.val(), 10), + preview: $(this).parent().parent().data('colpick').livePreview + }; + $(document).mouseup(current, upIncrement); + $(document).mousemove(current, moveIncrement); + }, + moveIncrement = function (ev) { + ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); + if (ev.data.preview) { + change.apply(ev.data.field.get(0), [true]); + } + return false; + }, + upIncrement = function (ev) { + change.apply(ev.data.field.get(0), [true]); + ev.data.el.removeClass('colpick_slider').find('input').focus(); + $(document).off('mouseup', upIncrement); + $(document).off('mousemove', moveIncrement); + return false; + }, + //Hue slider functions + downHue = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + y: $(this).offset().top + }; + $(document).on('mouseup touchend',current,upHue); + $(document).on('mousemove touchmove',current,moveHue); + + var pageY = ((ev.type == 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + current.cal.data('colpick') + .fields.eq(4).val(parseInt(360*(current.cal.data('colpick').height - (pageY - current.y))/current.cal.data('colpick').height, 10)) + .get(0), + [current.cal.data('colpick').livePreview] + ); + return false; + }, + moveHue = function (ev) { + var pageY = ((ev.type == 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + ev.data.cal.data('colpick') + .fields.eq(4).val(parseInt(360*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.y))))/ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upHue = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upHue); + $(document).off('mousemove touchmove',moveHue); + return false; + }, + //Color selector functions + downSelector = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + pos: $(this).offset() + }; + current.preview = current.cal.data('colpick').livePreview; + + $(document).on('mouseup touchend',current,upSelector); + $(document).on('mousemove touchmove',current,moveSelector); - var payeX,pageY; - if(ev.type == 'touchstart') { - pageX = ev.originalEvent.changedTouches[0].pageX, - pageY = ev.originalEvent.changedTouches[0].pageY; - } else { - pageX = ev.pageX; - pageY = ev.pageY; - } + var payeX,pageY; + if(ev.type == 'touchstart') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } - change.apply( - current.cal.data('colpick').fields - .eq(6).val(parseInt(100*(current.cal.data('colpick').height - (pageY - current.pos.top))/current.cal.data('colpick').height, 10)).end() - .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) - .get(0), - [current.preview] - ); - return false; - }, - moveSelector = function (ev) { - var payeX,pageY; - if(ev.type == 'touchmove') { - pageX = ev.originalEvent.changedTouches[0].pageX, - pageY = ev.originalEvent.changedTouches[0].pageY; - } else { - pageX = ev.pageX; - pageY = ev.pageY; - } + change.apply( + current.cal.data('colpick').fields + .eq(6).val(parseInt(100*(current.cal.data('colpick').height - (pageY - current.pos.top))/current.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) + .get(0), + [current.preview] + ); + return false; + }, + moveSelector = function (ev) { + var payeX,pageY; + if(ev.type == 'touchmove') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } - change.apply( - ev.data.cal.data('colpick').fields - .eq(6).val(parseInt(100*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.pos.top))))/ev.data.cal.data('colpick').height, 10)).end() - .eq(5).val(parseInt(100*(Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageX - ev.data.pos.left))))/ev.data.cal.data('colpick').height, 10)) - .get(0), - [ev.data.preview] - ); - return false; - }, - upSelector = function (ev) { - fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); - fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); - $(document).off('mouseup touchend',upSelector); - $(document).off('mousemove touchmove',moveSelector); - return false; - }, - //Submit button - clickSubmit = function (ev) { - var cal = $(this).parent(); - var col = cal.data('colpick').color; - cal.data('colpick').origColor = col; - setCurrentColor(col, cal.get(0)); - cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); - }, - //Show/hide the color picker - show = function (ev) { - // Prevent the trigger of any direct parent - ev.stopPropagation(); - var cal = $('#' + $(this).data('colpickId')); - cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); - var pos = $(this).offset(); - var top = pos.top + this.offsetHeight; - var left = pos.left; - var viewPort = getViewport(); - var calW = cal.width(); - if (left + calW > viewPort.l + viewPort.w) { - left -= calW; - } - cal.css({left: left + 'px', top: top + 'px'}); - if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) != false) { - cal.show(); - } - //Hide when user clicks outside - $('html').mousedown({cal:cal}, hide); - cal.mousedown(function(ev){ev.stopPropagation();}) - }, - hide = function (ev) { - if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) != false) { - ev.data.cal.hide(); - } - $('html').off('mousedown', hide); - }, - getViewport = function () { - var m = document.compatMode == 'CSS1Compat'; - return { - l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), - w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) - }; - }, - //Fix the values if the user enters a negative or high value - fixHSB = function (hsb) { - return { - h: Math.min(360, Math.max(0, hsb.h)), - s: Math.min(100, Math.max(0, hsb.s)), - b: Math.min(100, Math.max(0, hsb.b)) - }; - }, - fixRGB = function (rgb) { - return { - r: Math.min(255, Math.max(0, rgb.r)), - g: Math.min(255, Math.max(0, rgb.g)), - b: Math.min(255, Math.max(0, rgb.b)) - }; - }, - fixHex = function (hex) { - var len = 6 - hex.length; - if (len > 0) { - var o = []; - for (var i=0; i').attr('style','height:8.333333%; filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+')";'); - huebar.append(div); - } - } else { - stopList = stops.join(','); - huebar.attr('style','background:-webkit-linear-gradient(top,'+stopList+'); background: -o-linear-gradient(top,'+stopList+'); background: -ms-linear-gradient(top,'+stopList+'); background:-moz-linear-gradient(top,'+stopList+'); -webkit-linear-gradient(top,'+stopList+'); background:linear-gradient(to bottom,'+stopList+'); '); - } - cal.find('div.colpick_hue').on('mousedown touchstart',downHue); - options.newColor = cal.find('div.colpick_new_color'); - options.currentColor = cal.find('div.colpick_current_color'); - //Store options and fill with default color - cal.data('colpick', options); - fillRGBFields(options.color, cal.get(0)); - fillHSBFields(options.color, cal.get(0)); - fillHexFields(options.color, cal.get(0)); - setHue(options.color, cal.get(0)); - setSelector(options.color, cal.get(0)); - setCurrentColor(options.color, cal.get(0)); - setNewColor(options.color, cal.get(0)); - //Append to body if flat=false, else show in place - if (options.flat) { - cal.appendTo(this).show(); - cal.css({ - position: 'relative', - display: 'block' - }); - } else { - cal.appendTo(document.body); - $(this).on(options.showEvent, show); - cal.css({ - position:'absolute' - }); - } - } - }); - }, - //Shows the picker - showPicker: function() { - return this.each( function () { - if ($(this).data('colpickId')) { - show.apply(this); - } - }); - }, - //Hides the picker - hidePicker: function() { - return this.each( function () { - if ($(this).data('colpickId')) { - $('#' + $(this).data('colpickId')).hide(); - } - }); - }, - //Sets a color as new and current (default) - setColor: function(col, setCurrent) { - setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; - if (typeof col == 'string') { - col = hexToHsb(col); - } else if (col.r != undefined && col.g != undefined && col.b != undefined) { - col = rgbToHsb(col); - } else if (col.h != undefined && col.s != undefined && col.b != undefined) { - col = fixHSB(col); - } else { - return this; - } - return this.each(function(){ - if ($(this).data('colpickId')) { - var cal = $('#' + $(this).data('colpickId')); - cal.data('colpick').color = col; - cal.data('colpick').origColor = col; - fillRGBFields(col, cal.get(0)); - fillHSBFields(col, cal.get(0)); - fillHexFields(col, cal.get(0)); - setHue(col, cal.get(0)); - setSelector(col, cal.get(0)); - - setNewColor(col, cal.get(0)); - cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); - if(setCurrent) { - setCurrentColor(col, cal.get(0)); - } - } - }); - } - }; - }(); - //Color space convertions - var hexToRgb = function (hex) { - var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); - return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; - }; - var hexToHsb = function (hex) { - return rgbToHsb(hexToRgb(hex)); - }; - var rgbToHsb = function (rgb) { - var hsb = {h: 0, s: 0, b: 0}; - var min = Math.min(rgb.r, rgb.g, rgb.b); - var max = Math.max(rgb.r, rgb.g, rgb.b); - var delta = max - min; - hsb.b = max; - hsb.s = max != 0 ? 255 * delta / max : 0; - if (hsb.s != 0) { - if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta; - else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta; - else hsb.h = 4 + (rgb.r - rgb.g) / delta; - } else hsb.h = -1; - hsb.h *= 60; - if (hsb.h < 0) hsb.h += 360; - hsb.s *= 100/255; - hsb.b *= 100/255; - return hsb; - }; - var hsbToRgb = function (hsb) { - var rgb = {}; - var h = hsb.h; - var s = hsb.s*255/100; - var v = hsb.b*255/100; - if(s == 0) { - rgb.r = rgb.g = rgb.b = v; - } else { - var t1 = v; - var t2 = (255-s)*v/255; - var t3 = (t1-t2)*(h%60)/60; - if(h==360) h = 0; - if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} - else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} - else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} - else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} - else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} - else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} - else {rgb.r=0; rgb.g=0; rgb.b=0} - } - return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; - }; - var rgbToHex = function (rgb) { - var hex = [ - rgb.r.toString(16), - rgb.g.toString(16), - rgb.b.toString(16) - ]; - $.each(hex, function (nr, val) { - if (val.length == 1) { - hex[nr] = '0' + val; - } - }); - return hex.join(''); - }; - var hsbToHex = function (hsb) { - return rgbToHex(hsbToRgb(hsb)); - }; - $.fn.extend({ - colpick: colpick.init, - colpickHide: colpick.hidePicker, - colpickShow: colpick.showPicker, - colpickSetColor: colpick.setColor - }); - $.extend({ - colpick:{ - rgbToHex: rgbToHex, - rgbToHsb: rgbToHsb, - hsbToHex: hsbToHex, - hsbToRgb: hsbToRgb, - hexToHsb: hexToHsb, - hexToRgb: hexToRgb - } - }); + change.apply( + ev.data.cal.data('colpick').fields + .eq(6).val(parseInt(100*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.pos.top))))/ev.data.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageX - ev.data.pos.left))))/ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upSelector = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upSelector); + $(document).off('mousemove touchmove',moveSelector); + return false; + }, + //Submit button + clickSubmit = function (ev) { + var cal = $(this).parent(); + var col = cal.data('colpick').color; + cal.data('colpick').origColor = col; + setCurrentColor(col, cal.get(0)); + cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); + }, + //Show/hide the color picker + show = function (ev) { + // Prevent the trigger of any direct parent + ev.stopPropagation(); + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); + var pos = $(this).offset(); + var top = pos.top + this.offsetHeight; + var left = pos.left; + var viewPort = getViewport(); + var calW = cal.width(); + if (left + calW > viewPort.l + viewPort.w) { + left -= calW; + } + cal.css({left: left + 'px', top: top + 'px'}); + if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) != false) { + cal.show(); + } + //Hide when user clicks outside + $('html').mousedown({cal:cal}, hide); + cal.mousedown(function(ev){ev.stopPropagation();}) + }, + hide = function (ev) { + if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) != false) { + ev.data.cal.hide(); + } + $('html').off('mousedown', hide); + }, + getViewport = function () { + var m = document.compatMode == 'CSS1Compat'; + return { + l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), + w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) + }; + }, + //Fix the values if the user enters a negative or high value + fixHSB = function (hsb) { + return { + h: Math.min(360, Math.max(0, hsb.h)), + s: Math.min(100, Math.max(0, hsb.s)), + b: Math.min(100, Math.max(0, hsb.b)) + }; + }, + fixRGB = function (rgb) { + return { + r: Math.min(255, Math.max(0, rgb.r)), + g: Math.min(255, Math.max(0, rgb.g)), + b: Math.min(255, Math.max(0, rgb.b)) + }; + }, + fixHex = function (hex) { + var len = 6 - hex.length; + if (len > 0) { + var o = []; + for (var i=0; i').attr('style','height:8.333333%; filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+')";'); + huebar.append(div); + } + } else { + stopList = stops.join(','); + huebar.attr('style','background:-webkit-linear-gradient(top,'+stopList+'); background: -o-linear-gradient(top,'+stopList+'); background: -ms-linear-gradient(top,'+stopList+'); background:-moz-linear-gradient(top,'+stopList+'); -webkit-linear-gradient(top,'+stopList+'); background:linear-gradient(to bottom,'+stopList+'); '); + } + cal.find('div.colpick_hue').on('mousedown touchstart',downHue); + options.newColor = cal.find('div.colpick_new_color'); + options.currentColor = cal.find('div.colpick_current_color'); + //Store options and fill with default color + cal.data('colpick', options); + fillRGBFields(options.color, cal.get(0)); + fillHSBFields(options.color, cal.get(0)); + fillHexFields(options.color, cal.get(0)); + setHue(options.color, cal.get(0)); + setSelector(options.color, cal.get(0)); + setCurrentColor(options.color, cal.get(0)); + setNewColor(options.color, cal.get(0)); + //Append to body if flat=false, else show in place + if (options.flat) { + cal.appendTo(this).show(); + cal.css({ + position: 'relative', + display: 'block' + }); + } else { + cal.appendTo(document.body); + $(this).on(options.showEvent, show); + cal.css({ + position:'absolute' + }); + } + } + }); + }, + //Shows the picker + showPicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + show.apply(this); + } + }); + }, + //Hides the picker + hidePicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + $('#' + $(this).data('colpickId')).hide(); + } + }); + }, + //Sets a color as new and current (default) + setColor: function(col, setCurrent) { + setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; + if (typeof col == 'string') { + col = hexToHsb(col); + } else if (col.r != undefined && col.g != undefined && col.b != undefined) { + col = rgbToHsb(col); + } else if (col.h != undefined && col.s != undefined && col.b != undefined) { + col = fixHSB(col); + } else { + return this; + } + return this.each(function(){ + if ($(this).data('colpickId')) { + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').color = col; + cal.data('colpick').origColor = col; + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + setHue(col, cal.get(0)); + setSelector(col, cal.get(0)); + + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); + if(setCurrent) { + setCurrentColor(col, cal.get(0)); + } + } + }); + } + }; + }(); + //Color space convertions + var hexToRgb = function (hex) { + var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); + return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; + }; + var hexToHsb = function (hex) { + return rgbToHsb(hexToRgb(hex)); + }; + var rgbToHsb = function (rgb) { + var hsb = {h: 0, s: 0, b: 0}; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max != 0 ? 255 * delta / max : 0; + if (hsb.s != 0) { + if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta; + else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta; + else hsb.h = 4 + (rgb.r - rgb.g) / delta; + } else hsb.h = -1; + hsb.h *= 60; + if (hsb.h < 0) hsb.h += 360; + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + }; + var hsbToRgb = function (hsb) { + var rgb = {}; + var h = hsb.h; + var s = hsb.s*255/100; + var v = hsb.b*255/100; + if(s == 0) { + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255-s)*v/255; + var t3 = (t1-t2)*(h%60)/60; + if(h==360) h = 0; + if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} + else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} + else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} + else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} + else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} + else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} + else {rgb.r=0; rgb.g=0; rgb.b=0} + } + return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; + }; + var rgbToHex = function (rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function (nr, val) { + if (val.length == 1) { + hex[nr] = '0' + val; + } + }); + return hex.join(''); + }; + var hsbToHex = function (hsb) { + return rgbToHex(hsbToRgb(hsb)); + }; + $.fn.extend({ + colpick: colpick.init, + colpickHide: colpick.hidePicker, + colpickShow: colpick.showPicker, + colpickSetColor: colpick.setColor + }); + $.extend({ + colpick:{ + rgbToHex: rgbToHex, + rgbToHsb: rgbToHsb, + hsbToHex: hsbToHex, + hsbToRgb: hsbToRgb, + hexToHsb: hexToHsb, + hexToRgb: hexToRgb + } + }); })(jQuery); diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index b386953c7c..49231ca7c1 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -208,7 +208,7 @@ EntityPropertyDialogBox = (function () { array.push({ label: "Collisions Will Move:", type: "checkbox", value: properties.collisionsWillMove }); index++; array.push({ label: "Collision Sound URL:", value: properties.collisionSoundURL }); - index++; + index++; array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) }); index++; diff --git a/examples/mouseLook.js b/examples/mouseLook.js index db9bd06a20..880ec138c4 100644 --- a/examples/mouseLook.js +++ b/examples/mouseLook.js @@ -38,10 +38,10 @@ var mouseLook = (function () { keyboardID = 0; function onKeyPressEvent(event) { - if (event.text == 'M') { - active = !active; - updateMapping(); - } + if (event.text == 'M') { + active = !active; + updateMapping(); + } } function findInput(name) { @@ -67,8 +67,8 @@ var mouseLook = (function () { } function updateMapping() { - if (keyboardID != 0) { - if (active) { + if (keyboardID != 0) { + if (active) { // Turn mouselook on yawFromMouse = 0; pitchFromMouse = 0; @@ -113,10 +113,10 @@ var mouseLook = (function () { } } } - } else { + } else { Controller.resetDevice(keyboardID); - } - } + } + } } function onScriptUpdate(dt) { @@ -190,7 +190,7 @@ var mouseLook = (function () { function tearDown() { if (keyboardID != 0) { - Controller.resetDevice(keyboardID); + Controller.resetDevice(keyboardID); } cleanupMenu(); } diff --git a/examples/planets.js b/examples/planets.js index 650119feda..467c642fbf 100644 --- a/examples/planets.js +++ b/examples/planets.js @@ -43,81 +43,81 @@ var particles = []; // Create planets that will extert gravity on test particles for (var i = 0; i < planetTypes.length; i++) { // NOTE: rotationalVelocity is in radians/sec - var rotationalVelocity = (10 + Math.random() * 60) * DEGREES_TO_RADIANS; - var position = { x: planetTypes[i].x, y: planetTypes[i].y, z: planetTypes[i].z }; - position = Vec3.multiply(MAX_RANGE / 2, position); - position = Vec3.sum(center, position); - - planets.push(Entities.addEntity( - { type: "Sphere", - position: position, - dimensions: { x: planetTypes[i].radius, y: planetTypes[i].radius, z: planetTypes[i].radius }, - color: { red: planetTypes[i].red, green: planetTypes[i].green, blue: planetTypes[i].blue }, - gravity: { x: 0, y: 0, z: 0 }, - angularVelocity: { x: 0, y: rotationalVelocity, z: 0 }, - angularDamping: 0.0, - ignoreCollisions: false, - lifetime: LIFETIME, - collisionsWillMove: false })); + var rotationalVelocity = (10 + Math.random() * 60) * DEGREES_TO_RADIANS; + var position = { x: planetTypes[i].x, y: planetTypes[i].y, z: planetTypes[i].z }; + position = Vec3.multiply(MAX_RANGE / 2, position); + position = Vec3.sum(center, position); + + planets.push(Entities.addEntity( + { type: "Sphere", + position: position, + dimensions: { x: planetTypes[i].radius, y: planetTypes[i].radius, z: planetTypes[i].radius }, + color: { red: planetTypes[i].red, green: planetTypes[i].green, blue: planetTypes[i].blue }, + gravity: { x: 0, y: 0, z: 0 }, + angularVelocity: { x: 0, y: rotationalVelocity, z: 0 }, + angularDamping: 0.0, + ignoreCollisions: false, + lifetime: LIFETIME, + collisionsWillMove: false })); } Script.setTimeout(createParticles, 1000); function createParticles() { - // Create initial test particles that will move according to gravity from the planets - for (var i = 0; i < NUM_INITIAL_PARTICLES; i++) { - var radius = PARTICLE_MIN_SIZE + Math.random() * PARTICLE_MAX_SIZE; - var gray = Math.random() * 155; - var whichPlanet = Math.floor(Math.random() * planets.length); - var position = { x: (Math.random() - 0.5) * MAX_RANGE, y: (Math.random() - 0.5) * MAX_TRANSLATION, z: (Math.random() - 0.5) * MAX_RANGE }; - var separation = Vec3.length(position); - particles.push(Entities.addEntity( - { type: "Sphere", - position: Vec3.sum(center, position), - dimensions: { x: radius, y: radius, z: radius }, - color: { red: 100 + gray, green: 100 + gray, blue: 100 + gray }, - gravity: { x: 0, y: 0, z: 0 }, - angularVelocity: { x: 0, y: 0, z: 0 }, - velocity: Vec3.multiply(INITIAL_VELOCITY * Math.sqrt(separation), Vec3.normalize(Vec3.cross(position, { x: 0, y: 1, z: 0 }))), - ignoreCollisions: false, - damping: DAMPING, - lifetime: LIFETIME, - collisionsWillMove: true })); - } - Script.update.connect(update); + // Create initial test particles that will move according to gravity from the planets + for (var i = 0; i < NUM_INITIAL_PARTICLES; i++) { + var radius = PARTICLE_MIN_SIZE + Math.random() * PARTICLE_MAX_SIZE; + var gray = Math.random() * 155; + var whichPlanet = Math.floor(Math.random() * planets.length); + var position = { x: (Math.random() - 0.5) * MAX_RANGE, y: (Math.random() - 0.5) * MAX_TRANSLATION, z: (Math.random() - 0.5) * MAX_RANGE }; + var separation = Vec3.length(position); + particles.push(Entities.addEntity( + { type: "Sphere", + position: Vec3.sum(center, position), + dimensions: { x: radius, y: radius, z: radius }, + color: { red: 100 + gray, green: 100 + gray, blue: 100 + gray }, + gravity: { x: 0, y: 0, z: 0 }, + angularVelocity: { x: 0, y: 0, z: 0 }, + velocity: Vec3.multiply(INITIAL_VELOCITY * Math.sqrt(separation), Vec3.normalize(Vec3.cross(position, { x: 0, y: 1, z: 0 }))), + ignoreCollisions: false, + damping: DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true })); + } + Script.update.connect(update); } function scriptEnding() { - for (var i = 0; i < planetTypes.length; i++) { - Entities.deleteEntity(planets[i]); - } - for (var i = 0; i < particles.length; i++) { - Entities.deleteEntity(particles[i]); - } + for (var i = 0; i < planetTypes.length; i++) { + Entities.deleteEntity(planets[i]); + } + for (var i = 0; i < particles.length; i++) { + Entities.deleteEntity(particles[i]); + } } function update(deltaTime) { - // Apply gravitational force from planets - for (var t = 0; t < particles.length; t++) { - var properties1 = Entities.getEntityProperties(particles[t]); - var velocity = properties1.velocity; - var vColor = Vec3.length(velocity) / 50 * 255; - var dV = { x:0, y:0, z:0 }; - var mass1 = Math.pow(properties1.dimensions.x / 2.0, 3.0); - for (var p = 0; p < planets.length; p++) { - var properties2 = Entities.getEntityProperties(planets[p]); - var mass2 = Math.pow(properties2.dimensions.x / 2.0, 3.0); - var between = Vec3.subtract(properties1.position, properties2.position); - var separation = Vec3.length(between); - dV = Vec3.sum(dV, Vec3.multiply(-G * mass1 * mass2 / separation, Vec3.normalize(between))); - } - if (Math.random() < 0.1) { - Entities.editEntity(particles[t], { color: { red: vColor, green: 100, blue: (255 - vColor) }, velocity: Vec3.sum(velocity, Vec3.multiply(deltaTime, dV))}); - } else { - Entities.editEntity(particles[t], { velocity: Vec3.sum(velocity, Vec3.multiply(deltaTime, dV))}); - } - - } + // Apply gravitational force from planets + for (var t = 0; t < particles.length; t++) { + var properties1 = Entities.getEntityProperties(particles[t]); + var velocity = properties1.velocity; + var vColor = Vec3.length(velocity) / 50 * 255; + var dV = { x:0, y:0, z:0 }; + var mass1 = Math.pow(properties1.dimensions.x / 2.0, 3.0); + for (var p = 0; p < planets.length; p++) { + var properties2 = Entities.getEntityProperties(planets[p]); + var mass2 = Math.pow(properties2.dimensions.x / 2.0, 3.0); + var between = Vec3.subtract(properties1.position, properties2.position); + var separation = Vec3.length(between); + dV = Vec3.sum(dV, Vec3.multiply(-G * mass1 * mass2 / separation, Vec3.normalize(between))); + } + if (Math.random() < 0.1) { + Entities.editEntity(particles[t], { color: { red: vColor, green: 100, blue: (255 - vColor) }, velocity: Vec3.sum(velocity, Vec3.multiply(deltaTime, dV))}); + } else { + Entities.editEntity(particles[t], { velocity: Vec3.sum(velocity, Vec3.multiply(deltaTime, dV))}); + } + + } } Script.scriptEnding.connect(scriptEnding); diff --git a/examples/popcorn.js b/examples/popcorn.js index 265cc00257..ec24b962f2 100644 --- a/examples/popcorn.js +++ b/examples/popcorn.js @@ -25,160 +25,160 @@ var TWO_PI = 2.0 * Math.PI; var center = Vec3.sum(MyAvatar.position, Vec3.multiply(SCALE * 3.0, Quat.getFront(Camera.getOrientation()))); var floor = Entities.addEntity( - { type: "Box", - position: Vec3.subtract(center, { x: 0, y: SCALE / 2.0, z: 0 }), - dimensions: { x: SCALE, y: WALL_THICKNESS, z: SCALE }, - color: { red: 0, green: 255, blue: 0 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.subtract(center, { x: 0, y: SCALE / 2.0, z: 0 }), + dimensions: { x: SCALE, y: WALL_THICKNESS, z: SCALE }, + color: { red: 0, green: 255, blue: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + lifetime: LIFETIME }); var ceiling = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: 0, y: SCALE / 2.0, z: 0 }), - dimensions: { x: SCALE, y: WALL_THICKNESS, z: SCALE }, - color: { red: 128, green: 128, blue: 128 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: 0, y: SCALE / 2.0, z: 0 }), + dimensions: { x: SCALE, y: WALL_THICKNESS, z: SCALE }, + color: { red: 128, green: 128, blue: 128 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: true, + lifetime: LIFETIME }); var wall1 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: SCALE / 2.0, y: 0, z: 0 }), - dimensions: { x: WALL_THICKNESS, y: SCALE, z: SCALE }, - color: { red: 0, green: 255, blue: 0 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: false, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: SCALE / 2.0, y: 0, z: 0 }), + dimensions: { x: WALL_THICKNESS, y: SCALE, z: SCALE }, + color: { red: 0, green: 255, blue: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: false, + lifetime: LIFETIME }); var wall2 = Entities.addEntity( - { type: "Box", - position: Vec3.subtract(center, { x: SCALE / 2.0, y: 0, z: 0 }), - dimensions: { x: WALL_THICKNESS, y: SCALE, z: SCALE }, - color: { red: 0, green: 255, blue: 0 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: false, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.subtract(center, { x: SCALE / 2.0, y: 0, z: 0 }), + dimensions: { x: WALL_THICKNESS, y: SCALE, z: SCALE }, + color: { red: 0, green: 255, blue: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: false, + lifetime: LIFETIME }); var wall3 = Entities.addEntity( - { type: "Box", - position: Vec3.subtract(center, { x: 0, y: 0, z: SCALE / 2.0 }), - dimensions: { x: SCALE, y: SCALE, z: WALL_THICKNESS }, - color: { red: 0, green: 255, blue: 0 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: false, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.subtract(center, { x: 0, y: 0, z: SCALE / 2.0 }), + dimensions: { x: SCALE, y: SCALE, z: WALL_THICKNESS }, + color: { red: 0, green: 255, blue: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: false, + lifetime: LIFETIME }); var wall4 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: 0, y: 0, z: SCALE / 2.0 }), - dimensions: { x: SCALE, y: SCALE, z: WALL_THICKNESS }, - color: { red: 0, green: 255, blue: 0 }, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: false, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: 0, y: 0, z: SCALE / 2.0 }), + dimensions: { x: SCALE, y: SCALE, z: WALL_THICKNESS }, + color: { red: 0, green: 255, blue: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: false, + lifetime: LIFETIME }); var corner1 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: -SCALE / 2.0, y: 0, z: SCALE / 2.0 }), - dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, - color: { red: 128, green: 128, blue: 128 }, - ignoreCollisions: false, - visible: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: -SCALE / 2.0, y: 0, z: SCALE / 2.0 }), + dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, + color: { red: 128, green: 128, blue: 128 }, + ignoreCollisions: false, + visible: true, + lifetime: LIFETIME }); var corner2 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: -SCALE / 2.0, y: 0, z: -SCALE / 2.0 }), - dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, - color: { red: 128, green: 128, blue: 128 }, - ignoreCollisions: false, - visible: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: -SCALE / 2.0, y: 0, z: -SCALE / 2.0 }), + dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, + color: { red: 128, green: 128, blue: 128 }, + ignoreCollisions: false, + visible: true, + lifetime: LIFETIME }); var corner3 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: SCALE / 2.0, y: 0, z: SCALE / 2.0 }), - dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, - color: { red: 128, green: 128, blue: 128 }, - ignoreCollisions: false, - visible: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: SCALE / 2.0, y: 0, z: SCALE / 2.0 }), + dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, + color: { red: 128, green: 128, blue: 128 }, + ignoreCollisions: false, + visible: true, + lifetime: LIFETIME }); var corner4 = Entities.addEntity( - { type: "Box", - position: Vec3.sum(center, { x: SCALE / 2.0, y: 0, z: -SCALE / 2.0 }), - dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, - color: { red: 128, green: 128, blue: 128 }, - ignoreCollisions: false, - visible: true, - lifetime: LIFETIME }); + { type: "Box", + position: Vec3.sum(center, { x: SCALE / 2.0, y: 0, z: -SCALE / 2.0 }), + dimensions: { x: WALL_THICKNESS, y: SCALE, z: WALL_THICKNESS }, + color: { red: 128, green: 128, blue: 128 }, + ignoreCollisions: false, + visible: true, + lifetime: LIFETIME }); var spinner = Entities.addEntity( - { type: "Box", - position: center, - dimensions: { x: SCALE / 1.5, y: SCALE / 3.0, z: SCALE / 8.0 }, - color: { red: 255, green: 0, blue: 0 }, + { type: "Box", + position: center, + dimensions: { x: SCALE / 1.5, y: SCALE / 3.0, z: SCALE / 8.0 }, + color: { red: 255, green: 0, blue: 0 }, // NOTE: angularVelocity is in radians/sec - angularVelocity: { x: 0, y: TWO_PI, z: 0 }, - angularDamping: 0.0, - gravity: { x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - visible: true, - lifetime: LIFETIME }); + angularVelocity: { x: 0, y: TWO_PI, z: 0 }, + angularDamping: 0.0, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: true, + lifetime: LIFETIME }); var NUM_BALLS = 70; balls = []; for (var i = 0; i < NUM_BALLS; i++) { - balls.push(Entities.addEntity( - { type: "Sphere", - position: { x: center.x + (Math.random() - 0.5) * (SCALE - BALL_SIZE - WALL_THICKNESS), - y: center.y + (Math.random() - 0.5) * (SCALE - BALL_SIZE - WALL_THICKNESS) , - z: center.z + (Math.random() - 0.5) * (SCALE - BALL_SIZE - WALL_THICKNESS) }, - dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE }, - color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, - gravity: { x: 0, y: GRAVITY, z: 0 }, - ignoreCollisions: false, - damping: DAMPING, - lifetime: LIFETIME, - collisionsWillMove: true })); + balls.push(Entities.addEntity( + { type: "Sphere", + position: { x: center.x + (Math.random() - 0.5) * (SCALE - BALL_SIZE - WALL_THICKNESS), + y: center.y + (Math.random() - 0.5) * (SCALE - BALL_SIZE - WALL_THICKNESS) , + z: center.z + (Math.random() - 0.5) * (SCALE - BALL_SIZE - WALL_THICKNESS) }, + dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE }, + color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + ignoreCollisions: false, + damping: DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true })); } var VEL_MAG = 2.0; var CHANCE_OF_POP = 0.007; // 0.01; function update(deltaTime) { - for (var i = 0; i < NUM_BALLS; i++) { - if (Math.random() < CHANCE_OF_POP) { - Entities.editEntity(balls[i], { velocity: { x: (Math.random() - 0.5) * VEL_MAG, y: Math.random() * VEL_MAG, z: (Math.random() - 0.5) * VEL_MAG }}); - } - } + for (var i = 0; i < NUM_BALLS; i++) { + if (Math.random() < CHANCE_OF_POP) { + Entities.editEntity(balls[i], { velocity: { x: (Math.random() - 0.5) * VEL_MAG, y: Math.random() * VEL_MAG, z: (Math.random() - 0.5) * VEL_MAG }}); + } + } } function scriptEnding() { - Entities.deleteEntity(wall1); - Entities.deleteEntity(wall2); - Entities.deleteEntity(wall3); - Entities.deleteEntity(wall4); - Entities.deleteEntity(corner1); - Entities.deleteEntity(corner2); - Entities.deleteEntity(corner3); - Entities.deleteEntity(corner4); - Entities.deleteEntity(floor); - Entities.deleteEntity(ceiling); - Entities.deleteEntity(spinner); + Entities.deleteEntity(wall1); + Entities.deleteEntity(wall2); + Entities.deleteEntity(wall3); + Entities.deleteEntity(wall4); + Entities.deleteEntity(corner1); + Entities.deleteEntity(corner2); + Entities.deleteEntity(corner3); + Entities.deleteEntity(corner4); + Entities.deleteEntity(floor); + Entities.deleteEntity(ceiling); + Entities.deleteEntity(spinner); - for (var i = 0; i < NUM_BALLS; i++) { - Entities.deleteEntity(balls[i]); - } + for (var i = 0; i < NUM_BALLS; i++) { + Entities.deleteEntity(balls[i]); + } } Script.scriptEnding.connect(scriptEnding); diff --git a/examples/sit.js b/examples/sit.js index 7efad487a7..34a589bf99 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -53,43 +53,43 @@ var hiddingSeats = false; // This is the pose we would like to end up var pose = [ - {joint:"RightUpLeg", rotation: {x:100.0, y:15.0, z:0.0}}, - {joint:"RightLeg", rotation: {x:-130.0, y:15.0, z:0.0}}, - {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:"RightUpLeg", rotation: {x:100.0, y:15.0, z:0.0}}, + {joint:"RightLeg", rotation: {x:-130.0, y:15.0, z:0.0}}, + {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}} ]; var startPoseAndTransition = []; function storeStartPoseAndTransition() { - for (var i = 0; i < pose.length; i++){ - var startRotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(pose[i].joint)); - var transitionVector = Vec3.subtract( pose[i].rotation, startRotation ); - startPoseAndTransition.push({joint: pose[i].joint, start: startRotation, transition: transitionVector}); - } + for (var i = 0; i < pose.length; i++){ + var startRotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(pose[i].joint)); + var transitionVector = Vec3.subtract( pose[i].rotation, startRotation ); + startPoseAndTransition.push({joint: pose[i].joint, start: startRotation, transition: transitionVector}); + } } function updateJoints(factor){ - for (var i = 0; i < startPoseAndTransition.length; i++){ - var scaledTransition = Vec3.multiply(startPoseAndTransition[i].transition, factor); - var rotation = Vec3.sum(startPoseAndTransition[i].start, scaledTransition); - MyAvatar.setJointData(startPoseAndTransition[i].joint, Quat.fromVec3Degrees( rotation )); - } + for (var i = 0; i < startPoseAndTransition.length; i++){ + var scaledTransition = Vec3.multiply(startPoseAndTransition[i].transition, factor); + var rotation = Vec3.sum(startPoseAndTransition[i].start, scaledTransition); + MyAvatar.setJointData(startPoseAndTransition[i].joint, Quat.fromVec3Degrees( rotation )); + } } var sittingDownAnimation = function(deltaTime) { - passedTime += deltaTime; - var factor = passedTime/animationLenght; - - if ( passedTime <= animationLenght ) { - updateJoints(factor); + passedTime += deltaTime; + var factor = passedTime/animationLenght; + + if ( passedTime <= animationLenght ) { + updateJoints(factor); - var pos = { x: startPosition.x - 0.3 * factor, y: startPosition.y - 0.5 * factor, z: startPosition.z}; - MyAvatar.position = pos; - } else { + var pos = { x: startPosition.x - 0.3 * factor, y: startPosition.y - 0.5 * factor, z: startPosition.z}; + MyAvatar.position = pos; + } else { Script.update.disconnect(sittingDownAnimation); if (seat.model) { MyAvatar.setModelReferential(seat.model.id); @@ -99,16 +99,16 @@ var sittingDownAnimation = function(deltaTime) { var standingUpAnimation = function(deltaTime) { - passedTime += deltaTime; - var factor = 1 - passedTime/animationLenght; + passedTime += deltaTime; + var factor = 1 - passedTime/animationLenght; - if ( passedTime <= animationLenght ) { - - updateJoints(factor); + if ( passedTime <= animationLenght ) { + + updateJoints(factor); - var pos = { x: startPosition.x + 0.3 * (passedTime/animationLenght), y: startPosition.y + 0.5 * (passedTime/animationLenght), z: startPosition.z}; - MyAvatar.position = pos; - } else { + var pos = { x: startPosition.x + 0.3 * (passedTime/animationLenght), y: startPosition.y + 0.5 * (passedTime/animationLenght), z: startPosition.z}; + MyAvatar.position = pos; + } else { Script.update.disconnect(standingUpAnimation); } @@ -116,12 +116,12 @@ var standingUpAnimation = function(deltaTime) { var goToSeatAnimation = function(deltaTime) { passedTime += deltaTime; - var factor = passedTime/animationLenght; + var factor = passedTime/animationLenght; - if (passedTime <= animationLenght) { - var targetPosition = Vec3.sum(seat.position, { x: 0.3, y: 0.5, z: 0 }); - MyAvatar.position = Vec3.sum(Vec3.multiply(startPosition, 1 - factor), Vec3.multiply(targetPosition, factor)); - } else if (passedTime <= 2 * animationLenght) { + if (passedTime <= animationLenght) { + var targetPosition = Vec3.sum(seat.position, { x: 0.3, y: 0.5, z: 0 }); + MyAvatar.position = Vec3.sum(Vec3.multiply(startPosition, 1 - factor), Vec3.multiply(targetPosition, factor)); + } else if (passedTime <= 2 * animationLenght) { Quat.print("MyAvatar: ", MyAvatar.orientation); Quat.print("Seat: ", seat.rotation); MyAvatar.orientation = Quat.mix(startRotation, seat.rotation, factor - 1); @@ -133,35 +133,35 @@ var goToSeatAnimation = function(deltaTime) { } function sitDown() { - sitting = true; + sitting = true; Settings.setValue(sittingSettingsHandle, sitting); print("sitDown sitting status: " + Settings.getValue(sittingSettingsHandle, false)); - passedTime = 0.0; - startPosition = MyAvatar.position; - storeStartPoseAndTransition(); - try { - Script.update.disconnect(standingUpAnimation); - } catch(e){ - // no need to handle. if it wasn't connected no harm done - } - Script.update.connect(sittingDownAnimation); - Overlays.editOverlay(sitDownButton, { visible: false }); - Overlays.editOverlay(standUpButton, { visible: true }); + passedTime = 0.0; + startPosition = MyAvatar.position; + storeStartPoseAndTransition(); + try { + Script.update.disconnect(standingUpAnimation); + } catch(e){ + // no need to handle. if it wasn't connected no harm done + } + Script.update.connect(sittingDownAnimation); + Overlays.editOverlay(sitDownButton, { visible: false }); + Overlays.editOverlay(standUpButton, { visible: true }); } function standUp() { - sitting = false; + sitting = false; Settings.setValue(sittingSettingsHandle, sitting); print("standUp sitting status: " + Settings.getValue(sittingSettingsHandle, false)); - passedTime = 0.0; - startPosition = MyAvatar.position; - MyAvatar.clearReferential(); + passedTime = 0.0; + startPosition = MyAvatar.position; + MyAvatar.clearReferential(); try{ - Script.update.disconnect(sittingDownAnimation); - } catch (e){} - Script.update.connect(standingUpAnimation); - Overlays.editOverlay(standUpButton, { visible: false }); - Overlays.editOverlay(sitDownButton, { visible: true }); + Script.update.disconnect(sittingDownAnimation); + } catch (e){} + Script.update.connect(standingUpAnimation); + Overlays.editOverlay(standUpButton, { visible: false }); + Overlays.editOverlay(sitDownButton, { visible: true }); } var models = new Object(); @@ -203,14 +203,14 @@ function SeatIndicator(modelProperties, seatIndex) { } Controller.mousePressEvent.connect(function(event) { - var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - if (clickedOverlay == sitDownButton) { + if (clickedOverlay == sitDownButton) { seat.model = null; - sitDown(); - } else if (clickedOverlay == standUpButton) { + sitDown(); + } else if (clickedOverlay == standUpButton) { seat.model = null; - standUp(); + standUp(); } else { var pickRay = Camera.computePickRay(event.x, event.y); @@ -243,14 +243,14 @@ Controller.mousePressEvent.connect(function(event) { }) function update(deltaTime){ - var newWindowDimensions = Controller.getViewportDimensions(); - if( newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y ){ - windowDimensions = newWindowDimensions; - var newX = windowDimensions.x - buttonPadding - buttonWidth; - var newY = (windowDimensions.y - buttonHeight) / 2 ; - Overlays.editOverlay( standUpButton, {x: newX, y: newY} ); - Overlays.editOverlay( sitDownButton, {x: newX, y: newY} ); - } + var newWindowDimensions = Controller.getViewportDimensions(); + if( newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y ){ + windowDimensions = newWindowDimensions; + var newX = windowDimensions.x - buttonPadding - buttonWidth; + var newY = (windowDimensions.y - buttonHeight) / 2 ; + Overlays.editOverlay( standUpButton, {x: newX, y: newY} ); + Overlays.editOverlay( sitDownButton, {x: newX, y: newY} ); + } // For a weird reason avatar joint don't update till the 10th frame // Set the update frame to 20 to be safe @@ -276,7 +276,7 @@ function update(deltaTime){ oldHost = location.hostname; locationChanged = true; } - + if (MyAvatar.position.x != avatarOldPosition.x || MyAvatar.position.y != avatarOldPosition.y || MyAvatar.position.z != avatarOldPosition.z || @@ -354,9 +354,9 @@ function raySphereIntersection(origin, direction, center, radius) { function keyPressEvent(event) { if (event.text === ".") { if (sitting) { - standUp(); + standUp(); } else { - sitDown(); + sitDown(); } } } @@ -367,12 +367,12 @@ Controller.keyPressEvent.connect(keyPressEvent); Script.scriptEnding.connect(function() { MyAvatar.clearReferential(); - for (var i = 0; i < pose.length; i++){ - MyAvatar.clearJointData(pose[i].joint); - } + for (var i = 0; i < pose.length; i++){ + MyAvatar.clearJointData(pose[i].joint); + } - Overlays.deleteOverlay(sitDownButton); - Overlays.deleteOverlay(standUpButton); + Overlays.deleteOverlay(sitDownButton); + Overlays.deleteOverlay(standUpButton); for (model in models){ for (var i = 0; i < models[model].properties.sittingPoints.length; ++i) { models[model].properties.sittingPoints[i].indicator.cleanup(); diff --git a/examples/utilities/diagnostics/typedArraysUnitTest.js b/examples/utilities/diagnostics/typedArraysUnitTest.js index 0688667cc4..a50a40e43e 100644 --- a/examples/utilities/diagnostics/typedArraysUnitTest.js +++ b/examples/utilities/diagnostics/typedArraysUnitTest.js @@ -40,8 +40,8 @@ test('ArrayBuffer', function(finished) { this.assertEquals(new ArrayBuffer(123).byteLength, 123, 'length'); - this.raises(function () { return new ArrayBuffer(-1); }, 'negative length'); - this.raises(function () { return new ArrayBuffer(0x80000000); }, 'absurd length'); + this.raises(function () { return new ArrayBuffer(-1); }, 'negative length'); + this.raises(function () { return new ArrayBuffer(0x80000000); }, 'absurd length'); }); test('DataView constructors', function (finished) { diff --git a/examples/zones/jsstreamplayerdomain-zone-entity.js b/examples/zones/jsstreamplayerdomain-zone-entity.js index 9a8cb8b8b4..3730c71b4f 100644 --- a/examples/zones/jsstreamplayerdomain-zone-entity.js +++ b/examples/zones/jsstreamplayerdomain-zone-entity.js @@ -15,19 +15,19 @@ // Function which exists inside of an entity which triggers as a user approches it. (function() { - const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js"; - function isScriptRunning(script) { - script = script.toLowerCase().trim(); - var runningScripts = ScriptDiscoveryService.getRunning(); - for (i in runningScripts) { - if (runningScripts[i].url.toLowerCase().trim() == script) { - return true; - } - } - return false; - }; - - if (!isScriptRunning(SCRIPT_NAME)) { - Script.load(SCRIPT_NAME); - } + const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js"; + function isScriptRunning(script) { + script = script.toLowerCase().trim(); + var runningScripts = ScriptDiscoveryService.getRunning(); + for (i in runningScripts) { + if (runningScripts[i].url.toLowerCase().trim() == script) { + return true; + } + } + return false; + }; + + if (!isScriptRunning(SCRIPT_NAME)) { + Script.load(SCRIPT_NAME); + } }) \ No newline at end of file diff --git a/examples/zones/jsstreamplayerdomain-zone.js b/examples/zones/jsstreamplayerdomain-zone.js index 33d7364709..8e2195bd12 100644 --- a/examples/zones/jsstreamplayerdomain-zone.js +++ b/examples/zones/jsstreamplayerdomain-zone.js @@ -132,7 +132,7 @@ function toggleVisible(newVisibility) { // Function to check if avatar is in proper domain. Window.domainChanged.connect(function() { - Script.stop(); + Script.stop(); }); // Function to check if avatar is within zone. @@ -143,7 +143,7 @@ Entities.enterEntity.connect(function(entityID) { if(isOurZone(properties)) { lastZone = properties.name; - toggleVisible(true); + toggleVisible(true); } }) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 2a4e2b4300..fb2c88f75e 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -23,7 +23,7 @@ const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; const quint16 ICE_SERVER_MONITORING_PORT = 40110; IceServer::IceServer(int argc, char* argv[]) : - QCoreApplication(argc, argv), + QCoreApplication(argc, argv), _id(QUuid::createUuid()), _serverSocket(), _activePeers(), diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index d58cd8d7c4..bd50215de6 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -355,9 +355,9 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde } OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex]; - glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; - glm::vec3 pUp = position + orientation * IDENTITY_UP * size; - glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; + glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; + glm::vec3 pUp = position + orientation * IDENTITY_UP * size; + glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; glm::vec3 red(1.0f, 0.0f, 0.0f); geometryCache->renderLine(batch, position, pRight, red, jointLineIDs._right); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 3205aeda1d..ed2c1f022f 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -92,8 +92,8 @@ public: void start() { open(QIODevice::ReadOnly); } void stop() { close(); } - qint64 readData(char * data, qint64 maxSize); - qint64 writeData(const char * data, qint64 maxSize) { return 0; } + qint64 readData(char * data, qint64 maxSize); + qint64 writeData(const char * data, qint64 maxSize) { return 0; } int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; } private: diff --git a/libraries/audio/src/AudioSourceNoise.h b/libraries/audio/src/AudioSourceNoise.h index b7f6889179..80094d4ba9 100644 --- a/libraries/audio/src/AudioSourceNoise.h +++ b/libraries/audio/src/AudioSourceNoise.h @@ -23,11 +23,11 @@ class AudioSourceNoise static uint32_t _randomSeed; - int32_t _rows[_randomRows]; - int32_t _runningSum; // used to optimize summing of generators. - uint16_t _index; // incremented each sample. - uint16_t _indexMask; // index wrapped by ANDing with this mask. - float32_t _scale; // used to scale within range of -1.0 to +1.0 + int32_t _rows[_randomRows]; + int32_t _runningSum; // used to optimize summing of generators. + uint16_t _index; // incremented each sample. + uint16_t _indexMask; // index wrapped by ANDing with this mask. + float32_t _scale; // used to scale within range of -1.0 to +1.0 static uint32_t generateRandomNumber() { _randomSeed = (_randomSeed * 196314165) + 907633515; diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 3e790ff573..e2c3f69c39 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -53,10 +53,10 @@ public: void setOrientation(const glm::quat& orientation); float getAudioLoudness() const { return _audioLoudness; } - void setAudioLoudness(float audioLoudness) { _audioLoudness = audioLoudness; } + void setAudioLoudness(float audioLoudness) { _audioLoudness = audioLoudness; } float getAudioAverageLoudness() const { return _audioAverageLoudness; } - void setAudioAverageLoudness(float audioAverageLoudness) { _audioAverageLoudness = audioAverageLoudness; } + void setAudioAverageLoudness(float audioAverageLoudness) { _audioAverageLoudness = audioAverageLoudness; } void setBlendshape(QString name, float val); const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp index e823f456ce..c94268b336 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp @@ -232,7 +232,7 @@ bool OculusLegacyDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { } else { _hswDismissed = true; } - } + } #endif return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); } diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp index 1e3e7699f4..95e0ef741d 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -92,12 +92,12 @@ void OpenVrDisplayPlugin::activate() { CONTAINER->setIsOptionChecked(StandingHMDSensorMode, true); hmdRefCount++; - vr::HmdError eError = vr::HmdError_None; + vr::HmdError eError = vr::HmdError_None; if (!_hmd) { _hmd = vr::VR_Init(&eError); - Q_ASSERT(eError == vr::HmdError_None); - } - Q_ASSERT(_hmd); + Q_ASSERT(eError == vr::HmdError_None); + } + Q_ASSERT(_hmd); _hmd->GetWindowBounds(&_windowPosition.x, &_windowPosition.y, &_windowSize.x, &_windowSize.y); _hmd->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y); diff --git a/libraries/gpu/src/gpu/GLBackendShader.cpp b/libraries/gpu/src/gpu/GLBackendShader.cpp index 5f90313423..cd90da483b 100755 --- a/libraries/gpu/src/gpu/GLBackendShader.cpp +++ b/libraries/gpu/src/gpu/GLBackendShader.cpp @@ -386,21 +386,21 @@ ElementResource getFormatFromGLUniform(GLenum gltype) { case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); -/* {GL_FLOAT_MAT2x3 mat2x3}, - {GL_FLOAT_MAT2x4 mat2x4}, - {GL_FLOAT_MAT3x2 mat3x2}, - {GL_FLOAT_MAT3x4 mat3x4}, - {GL_FLOAT_MAT4x2 mat4x2}, - {GL_FLOAT_MAT4x3 mat4x3}, - {GL_DOUBLE_MAT2 dmat2}, - {GL_DOUBLE_MAT3 dmat3}, - {GL_DOUBLE_MAT4 dmat4}, - {GL_DOUBLE_MAT2x3 dmat2x3}, - {GL_DOUBLE_MAT2x4 dmat2x4}, - {GL_DOUBLE_MAT3x2 dmat3x2}, - {GL_DOUBLE_MAT3x4 dmat3x4}, - {GL_DOUBLE_MAT4x2 dmat4x2}, - {GL_DOUBLE_MAT4x3 dmat4x3}, +/* {GL_FLOAT_MAT2x3 mat2x3}, + {GL_FLOAT_MAT2x4 mat2x4}, + {GL_FLOAT_MAT3x2 mat3x2}, + {GL_FLOAT_MAT3x4 mat3x4}, + {GL_FLOAT_MAT4x2 mat4x2}, + {GL_FLOAT_MAT4x3 mat4x3}, + {GL_DOUBLE_MAT2 dmat2}, + {GL_DOUBLE_MAT3 dmat3}, + {GL_DOUBLE_MAT4 dmat4}, + {GL_DOUBLE_MAT2x3 dmat2x3}, + {GL_DOUBLE_MAT2x4 dmat2x4}, + {GL_DOUBLE_MAT3x2 dmat3x2}, + {GL_DOUBLE_MAT3x4 dmat3x4}, + {GL_DOUBLE_MAT4x2 dmat4x2}, + {GL_DOUBLE_MAT4x3 dmat4x3}, */ case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); @@ -423,12 +423,12 @@ ElementResource getFormatFromGLUniform(GLenum gltype) { case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); #endif -// {GL_SAMPLER_1D_SHADOW sampler1DShadow}, - // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, +// {GL_SAMPLER_1D_SHADOW sampler1DShadow}, + // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, -// {GL_SAMPLER_BUFFER samplerBuffer}, -// {GL_SAMPLER_2D_RECT sampler2DRect}, - // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, +// {GL_SAMPLER_BUFFER samplerBuffer}, +// {GL_SAMPLER_2D_RECT sampler2DRect}, + // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, #if defined(Q_OS_WIN) case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); @@ -441,8 +441,8 @@ ElementResource getFormatFromGLUniform(GLenum gltype) { case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, - // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, + // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, + // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); @@ -454,41 +454,41 @@ ElementResource getFormatFromGLUniform(GLenum gltype) { case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); #endif -// {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, -// {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, +// {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, +// {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, /* - {GL_IMAGE_1D image1D}, - {GL_IMAGE_2D image2D}, - {GL_IMAGE_3D image3D}, - {GL_IMAGE_2D_RECT image2DRect}, - {GL_IMAGE_CUBE imageCube}, - {GL_IMAGE_BUFFER imageBuffer}, - {GL_IMAGE_1D_ARRAY image1DArray}, - {GL_IMAGE_2D_ARRAY image2DArray}, - {GL_IMAGE_2D_MULTISAMPLE image2DMS}, - {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, - {GL_INT_IMAGE_1D iimage1D}, - {GL_INT_IMAGE_2D iimage2D}, - {GL_INT_IMAGE_3D iimage3D}, - {GL_INT_IMAGE_2D_RECT iimage2DRect}, - {GL_INT_IMAGE_CUBE iimageCube}, - {GL_INT_IMAGE_BUFFER iimageBuffer}, - {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, - {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, - {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, - {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, - {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, - {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, - {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, - {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, - {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot + {GL_IMAGE_1D image1D}, + {GL_IMAGE_2D image2D}, + {GL_IMAGE_3D image3D}, + {GL_IMAGE_2D_RECT image2DRect}, + {GL_IMAGE_CUBE imageCube}, + {GL_IMAGE_BUFFER imageBuffer}, + {GL_IMAGE_1D_ARRAY image1DArray}, + {GL_IMAGE_2D_ARRAY image2DArray}, + {GL_IMAGE_2D_MULTISAMPLE image2DMS}, + {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, + {GL_INT_IMAGE_1D iimage1D}, + {GL_INT_IMAGE_2D iimage2D}, + {GL_INT_IMAGE_3D iimage3D}, + {GL_INT_IMAGE_2D_RECT iimage2DRect}, + {GL_INT_IMAGE_CUBE iimageCube}, + {GL_INT_IMAGE_BUFFER iimageBuffer}, + {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, + {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, + {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, + {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, + {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, + {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, + {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, + {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, + {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot - {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, - {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, - {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} + {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, + {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, + {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, + {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, + {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, + {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} */ default: return ElementResource(Element(), Resource::BUFFER); diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index 72c7de8504..d4e1db125d 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -444,7 +444,7 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { break; } default: - qCDebug(gpulogging) << "GLBackend::syncGPUObject(const Texture&) case for Texture Type " << texture.getType() << " not supported"; + qCDebug(gpulogging) << "GLBackend::syncGPUObject(const Texture&) case for Texture Type " << texture.getType() << " not supported"; } (void) CHECK_GL_ERROR(); diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index 7f0abbd9b9..6723293dc1 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -43,12 +43,12 @@ void Mesh::addAttribute(Slot slot, const BufferView& buffer) { } const BufferView Mesh::getAttributeBuffer(int attrib) const { - auto attribBuffer = _attributeBuffers.find(attrib); - if (attribBuffer != _attributeBuffers.end()) { - return attribBuffer->second; - } else { - return BufferView(); - } + auto attribBuffer = _attributeBuffers.find(attrib); + if (attribBuffer != _attributeBuffers.end()) { + return attribBuffer->second; + } else { + return BufferView(); + } } void Mesh::evalVertexFormat() { diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 999cb23407..267769ad49 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -122,7 +122,7 @@ void Octree::recurseElementWithPostOperation(OctreeElement* element, RecurseOctr recurseElementWithPostOperation(child, operation, extraData, recursionCount+1); } } - operation(element, extraData); + operation(element, extraData); } // Recurses voxel tree calling the RecurseOctreeOperation function for each element. diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 244359e394..0b3dc0027f 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -350,8 +350,8 @@ public: void recurseElementWithOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount = 0); - /// Traverse child nodes of node applying operation in post-fix order - /// + /// Traverse child nodes of node applying operation in post-fix order + /// void recurseElementWithPostOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount = 0); diff --git a/libraries/octree/src/Plane.h b/libraries/octree/src/Plane.h index 0f481dee57..a113c753cc 100644 --- a/libraries/octree/src/Plane.h +++ b/libraries/octree/src/Plane.h @@ -19,28 +19,28 @@ class Plane { public: - Plane(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3) { set3Points(v1,v2,v3); } - Plane() : _normal(0,0,0), _point(0,0,0), _dCoefficient(0) {}; - ~Plane() {} ; + Plane(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3) { set3Points(v1,v2,v3); } + Plane() : _normal(0,0,0), _point(0,0,0), _dCoefficient(0) {}; + ~Plane() {} ; // methods for defining the plane - void set3Points(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3); - void setNormalAndPoint(const glm::vec3 &normal, const glm::vec3 &point); - void setCoefficients(float a, float b, float c, float d); + void set3Points(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3); + void setNormalAndPoint(const glm::vec3 &normal, const glm::vec3 &point); + void setCoefficients(float a, float b, float c, float d); - // getters - const glm::vec3& getNormal() const { return _normal; }; - const glm::vec3& getPoint() const { return _point; }; - float getDCoefficient() const { return _dCoefficient; }; + // getters + const glm::vec3& getNormal() const { return _normal; }; + const glm::vec3& getPoint() const { return _point; }; + float getDCoefficient() const { return _dCoefficient; }; // utilities - float distance(const glm::vec3 &point) const; - void print() const; + float distance(const glm::vec3 &point) const; + void print() const; private: - glm::vec3 _normal; - glm::vec3 _point; - float _dCoefficient; + glm::vec3 _normal; + glm::vec3 _point; + float _dCoefficient; }; diff --git a/libraries/render-utils/src/GlWindow.cpp b/libraries/render-utils/src/GlWindow.cpp index e97ee776dc..b71d0c9001 100644 --- a/libraries/render-utils/src/GlWindow.cpp +++ b/libraries/render-utils/src/GlWindow.cpp @@ -49,10 +49,10 @@ bool GlWindow::makeCurrent() { } void GlWindow::doneCurrent() { - _context->doneCurrent(); + _context->doneCurrent(); } void GlWindow::swapBuffers() { - _context->swapBuffers(this); + _context->swapBuffers(this); } diff --git a/libraries/script-engine/src/HFActionEvent.cpp b/libraries/script-engine/src/HFActionEvent.cpp index 780788cfca..3ed3bcb73c 100644 --- a/libraries/script-engine/src/HFActionEvent.cpp +++ b/libraries/script-engine/src/HFActionEvent.cpp @@ -12,7 +12,7 @@ #include "HFActionEvent.h" HFActionEvent::HFActionEvent(QEvent::Type type, const PickRay& actionRay) : - HFMetaEvent(type), + HFMetaEvent(type), actionRay(actionRay) { diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index b6c4f6c161..18a990cc9a 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -68,7 +68,7 @@ glm::vec3 AACube::getVertex(BoxVertex vertex) const { return _corner + glm::vec3(0, 0, _scale); case TOP_RIGHT_FAR: return _corner + glm::vec3(0, _scale, _scale); - default: //quiet windows warnings + default: //quiet windows warnings case TOP_LEFT_FAR: return _corner + glm::vec3(_scale, _scale, _scale); } @@ -350,7 +350,7 @@ glm::vec3 AACube::getClosestPointOnFace(const glm::vec3& point, BoxFace face) co return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), glm::vec3(_corner.x + _scale, _corner.y + _scale, _corner.z)); - default: //quiet windows warnings + default: //quiet windows warnings case MAX_Z_FACE: return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z + _scale), glm::vec3(_corner.x + _scale, _corner.y + _scale, _corner.z + _scale)); @@ -431,7 +431,7 @@ glm::vec4 AACube::getPlane(BoxFace face) const { case MIN_Y_FACE: return glm::vec4(0.0f, -1.0f, 0.0f, _corner.y); case MAX_Y_FACE: return glm::vec4(0.0f, 1.0f, 0.0f, -_corner.y - _scale); case MIN_Z_FACE: return glm::vec4(0.0f, 0.0f, -1.0f, _corner.z); - default: //quiet windows warnings + default: //quiet windows warnings case MAX_Z_FACE: return glm::vec4(0.0f, 0.0f, 1.0f, -_corner.z - _scale); } } @@ -443,7 +443,7 @@ BoxFace AACube::getOppositeFace(BoxFace face) { case MIN_Y_FACE: return MAX_Y_FACE; case MAX_Y_FACE: return MIN_Y_FACE; case MIN_Z_FACE: return MAX_Z_FACE; - default: //quiet windows warnings + default: //quiet windows warnings case MAX_Z_FACE: return MIN_Z_FACE; } } diff --git a/libraries/shared/src/BufferParser.h b/libraries/shared/src/BufferParser.h index d60e7127cd..bf06387a11 100644 --- a/libraries/shared/src/BufferParser.h +++ b/libraries/shared/src/BufferParser.h @@ -60,7 +60,7 @@ public: } inline size_t offset() const { - return _offset; + return _offset; } inline const uint8_t* data() const { diff --git a/libraries/shared/src/NumericalConstants.h b/libraries/shared/src/NumericalConstants.h index 2582e3358b..b632147641 100644 --- a/libraries/shared/src/NumericalConstants.h +++ b/libraries/shared/src/NumericalConstants.h @@ -24,7 +24,7 @@ const float PI_OVER_TWO = 0.5f * PI; const float RADIANS_PER_DEGREE = PI / 180.0f; const float DEGREES_PER_RADIAN = 180.0f / PI; -const float EPSILON = 0.000001f; //smallish positive number - used as margin of error for some computations +const float EPSILON = 0.000001f; //smallish positive number - used as margin of error for some computations const float SQUARE_ROOT_OF_2 = (float)sqrt(2.0f); const float SQUARE_ROOT_OF_3 = (float)sqrt(3.0f); const float METERS_PER_DECIMETER = 0.1f; diff --git a/libraries/shared/src/ThreadHelpers.h b/libraries/shared/src/ThreadHelpers.h index cc0e1ee666..0a92d36b80 100644 --- a/libraries/shared/src/ThreadHelpers.h +++ b/libraries/shared/src/ThreadHelpers.h @@ -17,13 +17,13 @@ template void withLock(L lock, F function) { - throw std::exception(); + throw std::exception(); } template void withLock(QMutex& lock, F function) { - QMutexLocker locker(&lock); - function(); + QMutexLocker locker(&lock); + function(); } #endif diff --git a/libraries/shared/src/windowshacks.h b/libraries/shared/src/windowshacks.h index 60ad650c24..e85a6f1237 100644 --- a/libraries/shared/src/windowshacks.h +++ b/libraries/shared/src/windowshacks.h @@ -18,7 +18,7 @@ #include inline double roundf(double value) { - return (value > 0.0) ? floor(value + 0.5) : ceil(value - 0.5); + return (value > 0.0) ? floor(value + 0.5) : ceil(value - 0.5); } #define round roundf @@ -34,10 +34,10 @@ inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) int count = -1; if (size != 0) { count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); - } + } if (count == -1) { count = _vscprintf(format, ap); - } + } return count; } diff --git a/tools/vhacd-util/src/VHACDUtil.h b/tools/vhacd-util/src/VHACDUtil.h index 34ad39a6f6..34c2c22e0e 100644 --- a/tools/vhacd-util/src/VHACDUtil.h +++ b/tools/vhacd-util/src/VHACDUtil.h @@ -46,7 +46,7 @@ namespace vhacd { // Couldn't follow coding guideline here due to virtual function declared in IUserCallback void Update(const double overallProgress, const double stageProgress, const double operationProgress, - const char * const stage, const char * const operation); + const char * const stage, const char * const operation); }; } From 13a9dc4df2eec31d3785da849c72c681054d7843 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 24 Aug 2015 15:35:55 -0700 Subject: [PATCH 157/170] cleanup controller interface, add getActionNames() and getDeviceNames() --- .../ControllerScriptingInterface.cpp | 18 +++++------ .../scripting/ControllerScriptingInterface.h | 2 ++ .../src/input-plugins/UserInputMapper.cpp | 31 ++++++++++++++++++- .../src/input-plugins/UserInputMapper.h | 7 +++-- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 5301429fd9..b182d3b339 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -469,20 +469,20 @@ int ControllerScriptingInterface::findDevice(QString name) { return DependencyManager::get()->findDevice(name); } +QVector ControllerScriptingInterface::getDeviceNames() { + return DependencyManager::get()->getDeviceNames(); +} + float ControllerScriptingInterface::getActionValue(int action) { return DependencyManager::get()->getActionState(UserInputMapper::Action(action)); } int ControllerScriptingInterface::findAction(QString actionName) { - auto userInputMapper = DependencyManager::get(); - auto actions = getAllActions(); - for (auto action : actions) { - if (userInputMapper->getActionName(action) == actionName) { - return action; - } - } - // If the action isn't found, return -1 - return -1; + return DependencyManager::get()->findAction(actionName); +} + +QVector ControllerScriptingInterface::getActionNames() const { + return DependencyManager::get()->getActionNames(); } InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) : diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index e63fa42a62..6956619bf0 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -101,8 +101,10 @@ public slots: Q_INVOKABLE virtual void resetDevice(unsigned int device); Q_INVOKABLE virtual void resetAllDeviceBindings(); Q_INVOKABLE virtual int findDevice(QString name); + Q_INVOKABLE virtual QVector getDeviceNames(); Q_INVOKABLE virtual int findAction(QString actionName); + Q_INVOKABLE virtual QVector getActionNames() const; virtual bool isPrimaryButtonPressed() const; virtual glm::vec2 getPrimaryJoystickPosition() const; diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp index 6c79ced280..b7533f39ec 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp @@ -62,6 +62,16 @@ int UserInputMapper::findDevice(QString name) { return 0; } +QVector UserInputMapper::getDeviceNames() { + QVector result; + for (auto device : _registeredDevices) { + QString deviceName = device.second->_name.split(" (")[0]; + result << deviceName; + } + return result; +} + + bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) { return addInputChannel(action, input, Input(), scale); } @@ -225,7 +235,7 @@ void UserInputMapper::update(float deltaTime) { } } -QVector UserInputMapper::getAllActions() { +QVector UserInputMapper::getAllActions() const { QVector actions; for (auto i = 0; i < NUM_ACTIONS; i++) { actions.append(Action(i)); @@ -243,6 +253,25 @@ QVector UserInputMapper::getInputChannelsForActio return inputChannels; } +int UserInputMapper::findAction(const QString& actionName) const { + auto actions = getAllActions(); + for (auto action : actions) { + if (getActionName(action) == actionName) { + return action; + } + } + // If the action isn't found, return -1 + return -1; +} + +QVector UserInputMapper::getActionNames() const { + QVector result; + for (auto i = 0; i < NUM_ACTIONS; i++) { + result << _actionNames[i]; + } + return result; +} + void UserInputMapper::assignDefaulActionScales() { _actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit _actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.h b/libraries/input-plugins/src/input-plugins/UserInputMapper.h index 7aa775141a..184b8d4776 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.h +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.h @@ -128,6 +128,7 @@ public: void resetAllDeviceBindings(); void resetDevice(uint16 deviceID); int findDevice(QString name); + QVector getDeviceNames(); // Actions are the output channels of the Mapper, that's what the InputChannel map to // For now the Actions are hardcoded, this is bad, but we will fix that in the near future @@ -167,10 +168,12 @@ public: std::vector _actionNames = std::vector(NUM_ACTIONS); void createActionNames(); - QVector getAllActions(); - QString getActionName(Action action) { return UserInputMapper::_actionNames[(int) action]; } + QVector getAllActions() const; + QString getActionName(Action action) const { return UserInputMapper::_actionNames[(int) action]; } float getActionState(Action action) const { return _actionStates[action]; } PoseValue getPoseState(Action action) const { return _poseStates[action]; } + int findAction(const QString& actionName) const; + QVector getActionNames() const; void assignDefaulActionScales(); // Add input channel to the mapper and check that all the used channels are registered. From b8ac9aa5ebaff67486ebc7e8ae4a6fd8f93f9474 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 18:02:26 -0700 Subject: [PATCH 158/170] update jquery and bootstrap in DS --- .../resources/web/assignment/index.shtml | 2 +- .../resources/web/css/bootstrap.min.css | 4 +- .../resources/web/css/datatables.min.css | 15 + .../fonts/glyphicons-halflings-regular.eot | Bin 14079 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 480 ++++++++++-------- .../fonts/glyphicons-halflings-regular.ttf | Bin 29512 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 16448 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes domain-server/resources/web/footer.html | 2 +- .../resources/web/js/bootstrap.min.js | 8 +- .../resources/web/js/datatables.min.js | 195 +++++++ .../resources/web/js/jquery-2.1.4.min.js | 4 + domain-server/resources/web/js/jquery.min.js | 4 - domain-server/resources/web/stats/js/stats.js | 1 + 14 files changed, 493 insertions(+), 222 deletions(-) create mode 100755 domain-server/resources/web/css/datatables.min.css create mode 100644 domain-server/resources/web/fonts/glyphicons-halflings-regular.woff2 create mode 100755 domain-server/resources/web/js/datatables.min.js create mode 100755 domain-server/resources/web/js/jquery-2.1.4.min.js delete mode 100644 domain-server/resources/web/js/jquery.min.js diff --git a/domain-server/resources/web/assignment/index.shtml b/domain-server/resources/web/assignment/index.shtml index b8f4fbb637..6643479d8c 100644 --- a/domain-server/resources/web/assignment/index.shtml +++ b/domain-server/resources/web/assignment/index.shtml @@ -6,7 +6,7 @@
- +
diff --git a/domain-server/resources/web/css/bootstrap.min.css b/domain-server/resources/web/css/bootstrap.min.css index cd1c616ad8..d65c66b1ba 100644 --- a/domain-server/resources/web/css/bootstrap.min.css +++ b/domain-server/resources/web/css/bootstrap.min.css @@ -1,5 +1,5 @@ /*! - * Bootstrap v3.3.4 (http://getbootstrap.com) + * Bootstrap v3.3.5 (http://getbootstrap.com) * Copyright 2011-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px)and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px)and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px)and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/domain-server/resources/web/css/datatables.min.css b/domain-server/resources/web/css/datatables.min.css new file mode 100755 index 0000000000..209dbce49d --- /dev/null +++ b/domain-server/resources/web/css/datatables.min.css @@ -0,0 +1,15 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#bs/jq-2.1.4,dt-1.10.8 + * + * Included libraries: + * jQuery 2.1.4, DataTables 1.10.8 + */ + +table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}table.dataTable thead>tr>th,table.dataTable thead>tr>td{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable{border-collapse:separate !important}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0} + + diff --git a/domain-server/resources/web/fonts/glyphicons-halflings-regular.eot b/domain-server/resources/web/fonts/glyphicons-halflings-regular.eot index 87eaa434234e2a984c261e0450a2f4ad837aa7b4..b93a4953fff68df523aa7656497ee339d6026d64 100644 GIT binary patch delta 19939 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 delta 13843 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 - - + + - - + + - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/domain-server/resources/web/fonts/glyphicons-halflings-regular.ttf b/domain-server/resources/web/fonts/glyphicons-halflings-regular.ttf index be784dc1d5bcb92ab155f578f3723524a3dd9688..1413fc609ab6f21774de0cb7e01360095584f65b 100644 GIT binary patch literal 45404 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 by=}d{)}5j^Ap|AKdGn|Ti_4chTMF>MA%94* literal 29512 zcmd75349aRwJ?6p%t&L|vgDC8l2=({*^&)Hwj@ikF&F|E0+^kEv#~|&n?n+oglv?o z4a8X>3$&r6O(-GYP_nd-^rf_+lcjBvI%z^*Qafo=UYq2lgl=Gs|G#r*WD5gb{@?e0 zzi-TFmOFFKxp&Sz_uR8w00F=bw*e3BM08qJX_4OO~*c|Nu zi|z*CK3%o;nuV`@(LEYqaXSFMw0hyjb_~NC086WhzBLP1FYem;STDfRO9A-J?Q5^Q z{=I8vYyepC6M&pe8x}8GGP!2P+W^;%1^_bvQ0?K(k$ZnZ$DjSxvy&bFa`tIfVgmrG zIRFO;2qyrkHGl&EUbr1Qzytzs<=z5-EA%6N8=~Ax^oohSsBiQ_X1i?i+^H}ce0_bU zjeVz~)bub|Gn5#ck6|YOJOEJJu7XfQz`_+TjP@#Xqz zee-<>DjWd}T7vd&zT47W)LqjZ?QZNI+uhPVvHSAw1>MWLH+A3B{YVerlhaewQ_)k? z6Ypv0xumD1XJXIgJ2+e9}I#F-KOq>ZeMp@cYXKh?w0Ne z-P5}lbT8^|@4l_OqX&CTJq10!p30uOo@CGHp0Pcb_Dt_t(9_;?TTe&NBLunclfDyu zOS;A1eDa$kzghMXe?tDi_JPG$?XDaV2*UHo7e=Yx0`IqH?DF1EwN98w^ zUthkud};ZT@&)Dd%iGH5mS0vrt$bqn*z%F($?}BvExon=S2p@yz};uSjO1riC>BUx=jGx762#%=q>>0E&_lGfNmc^ca6?d-E{!nQR;U9-Sq(7jR4RH zK>e$GEC7rG=x)(>7z@xn0ib�E`3Zo(|A`IRMbT1^Ny%0J;|ebT0>hnE>7G0NtCY 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 diff --git a/domain-server/resources/web/fonts/glyphicons-halflings-regular.woff b/domain-server/resources/web/fonts/glyphicons-halflings-regular.woff index 2cc3e4852a5a42e6aadd6284e067b66e14a57bc7..9e612858f802245ddcbf59788a0db942224bab35 100644 GIT binary patch literal 23424 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 literal 16448 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 diff --git a/domain-server/resources/web/fonts/glyphicons-halflings-regular.woff2 b/domain-server/resources/web/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/domain-server/resources/web/footer.html b/domain-server/resources/web/footer.html index 2512e25eb1..e8ea392b49 100644 --- a/domain-server/resources/web/footer.html +++ b/domain-server/resources/web/footer.html @@ -1,4 +1,4 @@
- + diff --git a/domain-server/resources/web/js/bootstrap.min.js b/domain-server/resources/web/js/bootstrap.min.js index c8f82e592a..133aeecb98 100644 --- a/domain-server/resources/web/js/bootstrap.min.js +++ b/domain-server/resources/web/js/bootstrap.min.js @@ -1,7 +1,7 @@ /*! - * Bootstrap v3.3.4 (http://getbootstrap.com) + * Bootstrap v3.3.5 (http://getbootstrap.com) * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * Licensed under the MIT license */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/domain-server/resources/web/js/datatables.min.js b/domain-server/resources/web/js/datatables.min.js new file mode 100755 index 0000000000..56d879475d --- /dev/null +++ b/domain-server/resources/web/js/datatables.min.js @@ -0,0 +1,195 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#bs/jq-2.1.4,dt-1.10.8 + * + * Included libraries: + * jQuery 2.1.4, DataTables 1.10.8 + */ + +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="
",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("