From 59a758b5ecdb9c71841b6de34585c89d2bee5dde Mon Sep 17 00:00:00 2001 From: Raffi Bedikian Date: Sun, 30 Aug 2015 20:38:05 -0700 Subject: [PATCH 01/12] Add AA state to RenderContext and Menu --- interface/src/Application.cpp | 1 + interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + libraries/render/src/render/Engine.h | 1 + 4 files changed, 4 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c227e5e1c9..88ebf25da8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3542,6 +3542,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect(); renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion); + renderContext._fxaaStatus = Menu::getInstance()->isOptionChecked(MenuOption::Antialiasing); renderArgs->_shouldRender = LODManager::shouldRender; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4696463181..a93f5e0425 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -317,6 +317,7 @@ Menu::Menu() { 0, // QML Qt::SHIFT | Qt::Key_A, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Antialiasing); MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 94e49abcc7..12aee08994 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -134,6 +134,7 @@ namespace MenuOption { const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; const QString AlternateIK = "Alternate IK"; const QString Animations = "Animations..."; + const QString Antialiasing = "Antialiasing"; const QString Atmosphere = "Atmosphere"; const QString Attachments = "Attachments..."; const QString AudioNoiseReduction = "Audio Noise Reduction"; diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index 8d096c5e15..5da7956b22 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -53,6 +53,7 @@ public: bool _drawHitEffect = false; bool _occlusionStatus = false; + bool _fxaaStatus = false; RenderContext() {} }; From 1001cf9000178e2117bf5372db47054c002f0f7b Mon Sep 17 00:00:00 2001 From: Raffi Bedikian Date: Sun, 30 Aug 2015 20:48:06 -0700 Subject: [PATCH 02/12] Add shader files for FXAA --- libraries/render-utils/src/fxaa.slf | 77 +++++++++++++++++++++++ libraries/render-utils/src/fxaa.slv | 26 ++++++++ libraries/render-utils/src/fxaa_blend.slf | 24 +++++++ 3 files changed, 127 insertions(+) create mode 100644 libraries/render-utils/src/fxaa.slf create mode 100644 libraries/render-utils/src/fxaa.slv create mode 100644 libraries/render-utils/src/fxaa_blend.slf diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf new file mode 100644 index 0000000000..1f10756ce6 --- /dev/null +++ b/libraries/render-utils/src/fxaa.slf @@ -0,0 +1,77 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// fxaa.frag +// fragment shader +// +// Created by Raffi Bedikian on 8/30/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 +// + +// FXAA shader, GLSL code adapted from: +// http://horde3d.org/wiki/index.php5?title=Shading_Technique_-_FXAA +// Whitepaper describing the technique: +// http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf + +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D colorTexture; +uniform vec2 texcoordOffset; + +in vec2 varTexcoord; +out vec4 outFragColor; + +void main() { + float FXAA_SPAN_MAX = 8.0; + float FXAA_REDUCE_MUL = 1.0/8.0; + float FXAA_REDUCE_MIN = (1.0/128.0); + + vec3 rgbNW = texture2D(colorTexture, varTexcoord + (vec2(-1.0, -1.0) * texcoordOffset)).xyz; + vec3 rgbNE = texture2D(colorTexture, varTexcoord + (vec2(+1.0, -1.0) * texcoordOffset)).xyz; + vec3 rgbSW = texture2D(colorTexture, varTexcoord + (vec2(-1.0, +1.0) * texcoordOffset)).xyz; + vec3 rgbSE = texture2D(colorTexture, varTexcoord + (vec2(+1.0, +1.0) * texcoordOffset)).xyz; + vec3 rgbM = texture2D(colorTexture, varTexcoord).xyz; + + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot( rgbM, luma); + + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + + float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce); + + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset; + + vec3 rgbA = (1.0/2.0) * ( + texture2D(colorTexture, varTexcoord + dir * (1.0/3.0 - 0.5)).xyz + + texture2D(colorTexture, varTexcoord + dir * (2.0/3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * ( + texture2D(colorTexture, varTexcoord + dir * (0.0/3.0 - 0.5)).xyz + + texture2D(colorTexture, varTexcoord + dir * (3.0/3.0 - 0.5)).xyz); + float lumaB = dot(rgbB, luma); + + if (lumaB < lumaMin || lumaB > lumaMax) { + outFragColor.xyz=rgbA; + } else { + outFragColor.xyz=rgbB; + } + outFragColor.a = 1.0; +} diff --git a/libraries/render-utils/src/fxaa.slv b/libraries/render-utils/src/fxaa.slv new file mode 100644 index 0000000000..35a96ceb24 --- /dev/null +++ b/libraries/render-utils/src/fxaa.slv @@ -0,0 +1,26 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// fxaa.vert +// vertex shader +// +// Created by Raffi Bedikian on 8/30/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 +// + +<@include gpu/Inputs.slh@> + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +out vec2 varTexcoord; + +void main(void) { + varTexcoord = inTexCoord0.xy; + gl_Position = inPosition; +} diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf new file mode 100644 index 0000000000..d5819cc9a6 --- /dev/null +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -0,0 +1,24 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// fxaa_blend.frag +// fragment shader +// +// Created by Raffi Bedikian on 8/30/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 +// + +<@include DeferredBufferWrite.slh@> + +in vec2 varTexcoord; +out vec4 outFragColor; + +uniform sampler2D colorTexture; + +void main(void) { + outFragColor = texture(colorTexture, varTexcoord); +} From ff2a58b3eb622e3b6d84c4e1513b87e1e696dade Mon Sep 17 00:00:00 2001 From: Raffi Bedikian Date: Sun, 30 Aug 2015 20:49:03 -0700 Subject: [PATCH 03/12] Add FXAA effect class, modeled after AO effect --- .../render-utils/src/AntialiasingEffect.cpp | 151 ++++++++++++++++++ .../render-utils/src/AntialiasingEffect.h | 44 +++++ .../render-utils/src/RenderDeferredTask.cpp | 8 + .../render-utils/src/RenderDeferredTask.h | 5 + 4 files changed, 208 insertions(+) create mode 100644 libraries/render-utils/src/AntialiasingEffect.cpp create mode 100644 libraries/render-utils/src/AntialiasingEffect.h diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp new file mode 100644 index 0000000000..4e1b6f451c --- /dev/null +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -0,0 +1,151 @@ +// +// AntialiasingEffect.cpp +// libraries/render-utils/src/ +// +// Created by Raffi Bedikian on 8/30/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 +// + + +#include + +#include +#include +#include + +#include "gpu/StandardShaderLib.h" +#include "AntialiasingEffect.h" +#include "TextureCache.h" +#include "FramebufferCache.h" +#include "DependencyManager.h" +#include "ViewFrustum.h" +#include "GeometryCache.h" + +#include "fxaa_vert.h" +#include "fxaa_frag.h" +#include "fxaa_blend_frag.h" + + +Antialiasing::Antialiasing() { +} + +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { + if (!_antialiasingPipeline) { + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); + auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); + + gpu::Shader::makeProgram(*program, slotBindings); + + _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset"); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + state->setDepthTest(false, false, gpu::LESS_EQUAL); + + // Link the antialiasing FBO to texture + _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, + DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height())); + auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); + auto width = _antialiasingBuffer->getWidth(); + auto height = _antialiasingBuffer->getHeight(); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); + + // Good to go add the brand new pipeline + _antialiasingPipeline.reset(gpu::Pipeline::create(program, state)); + } + return _antialiasingPipeline; +} + +const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { + if (!_blendPipeline) { + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); + auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_blend_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + state->setDepthTest(false, false, gpu::LESS_EQUAL); + + // Good to go add the brand new pipeline + _blendPipeline.reset(gpu::Pipeline::create(program, state)); + } + return _blendPipeline; +} + +void Antialiasing::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { + assert(renderContext->args); + assert(renderContext->args->_viewFrustum); + + 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; + args->_viewFrustum->evalProjectionMatrix(projMat); + args->_viewFrustum->evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + batch.setModelTransform(Transform()); + + // FXAA step + getAntialiasingPipeline(); + batch.setResourceTexture(0, framebufferCache->getPrimaryColorTexture()); + _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); + batch.setFramebuffer(_antialiasingBuffer); + batch.setPipeline(getAntialiasingPipeline()); + + // 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); + + float depthScale = (farVal - nearVal) / farVal; + 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(_texcoordOffsetLoc, 1.0 / fbWidth, 1.0 / fbHeight); + + glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); + glm::vec2 bottomLeft(-1.0f, -1.0f); + glm::vec2 topRight(1.0f, 1.0f); + glm::vec2 texCoordTopLeft(0.0f, 0.0f); + glm::vec2 texCoordBottomRight(1.0f, 1.0f); + DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); + + // Blend step + getBlendPipeline(); + batch.setResourceTexture(0, _antialiasingTexture); + batch.setFramebuffer(framebufferCache->getPrimaryFramebuffer()); + batch.setPipeline(getBlendPipeline()); + + DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); + + // Ready to render + args->_context->render((batch)); +} diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h new file mode 100644 index 0000000000..96cf3aaf3e --- /dev/null +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -0,0 +1,44 @@ +// +// AntialiasingEffect.h +// libraries/render-utils/src/ +// +// Created by Raffi Bedikian on 8/30/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 +// + +#ifndef hifi_AntialiasingEffect_h +#define hifi_AntialiasingEffect_h + +#include + +#include "render/DrawTask.h" + +class Antialiasing { +public: + + Antialiasing(); + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + typedef render::Job::Model JobModel; + + const gpu::PipelinePointer& getAntialiasingPipeline(); + const gpu::PipelinePointer& getBlendPipeline(); + +private: + + // Uniforms for AA + gpu::int32 _texcoordOffsetLoc; + + gpu::FramebufferPointer _antialiasingBuffer; + + gpu::TexturePointer _antialiasingTexture; + + gpu::PipelinePointer _antialiasingPipeline; + gpu::PipelinePointer _blendPipeline; + +}; + +#endif // hifi_AntialiasingEffect_h diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index ca3f87f53f..137294c5b6 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -23,6 +23,7 @@ #include "render/DrawStatus.h" #include "AmbientOcclusionEffect.h" +#include "AntialiasingEffect.h" #include "overlay3D_vert.h" #include "overlay3D_frag.h" @@ -88,6 +89,11 @@ RenderDeferredTask::RenderDeferredTask() : Task() { _jobs.back().setEnabled(false); _occlusionJobIndex = _jobs.size() - 1; + _jobs.push_back(Job(new Antialiasing::JobModel("Antialiasing"))); + + _jobs.back().setEnabled(false); + _antialiasingJobIndex = _jobs.size() - 1; + _jobs.push_back(Job(new FetchItems::JobModel("FetchTransparent", FetchItems( ItemFilter::Builder::transparentShape().withoutLayered(), @@ -146,6 +152,8 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend // TODO: turn on/off AO through menu item setOcclusionStatus(renderContext->_occlusionStatus); + setAntialiasingStatus(renderContext->_fxaaStatus); + renderContext->args->_context->syncCache(); for (auto job : _jobs) { diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 0041f5d9aa..8366a2665d 100755 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -91,6 +91,11 @@ public: void setOcclusionStatus(bool draw) { if (_occlusionJobIndex >= 0) { _jobs[_occlusionJobIndex].setEnabled(draw); } } bool doOcclusionStatus() const { if (_occlusionJobIndex >= 0) { return _jobs[_occlusionJobIndex].isEnabled(); } else { return false; } } + int _antialiasingJobIndex = -1; + + void setAntialiasingStatus(bool draw) { if (_antialiasingJobIndex >= 0) { _jobs[_antialiasingJobIndex].setEnabled(draw); } } + bool doAntialiasingStatus() const { if (_antialiasingJobIndex >= 0) { return _jobs[_antialiasingJobIndex].isEnabled(); } else { return false; } } + virtual void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); From 9318661bc38ac18cf6c8d76d411a2c25cf302d5c Mon Sep 17 00:00:00 2001 From: Raffi Bedikian Date: Tue, 1 Sep 2015 23:54:48 -0700 Subject: [PATCH 04/12] Add algorithm comments to FXAA shader --- libraries/render-utils/src/fxaa.slf | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 1f10756ce6..69596e13ce 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -29,16 +29,28 @@ in vec2 varTexcoord; out vec4 outFragColor; void main() { + // filter width limit for dependent "two-tap" texture samples float FXAA_SPAN_MAX = 8.0; - float FXAA_REDUCE_MUL = 1.0/8.0; - float FXAA_REDUCE_MIN = (1.0/128.0); + // local contrast multiplier for performing AA + // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail + // see "fxaaQualityEdgeThreshold" + float FXAA_REDUCE_MUL = 1.0/8.0; + + // luminance threshold for processing dark colors + // see "fxaaQualityEdgeThresholdMin" + float FXAA_REDUCE_MIN = (1.0/128.0); + + // fetch raw RGB values for nearby locations + // sampling pattern is "five on a die" (each diagonal direction and the center) + // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed vec3 rgbNW = texture2D(colorTexture, varTexcoord + (vec2(-1.0, -1.0) * texcoordOffset)).xyz; vec3 rgbNE = texture2D(colorTexture, varTexcoord + (vec2(+1.0, -1.0) * texcoordOffset)).xyz; vec3 rgbSW = texture2D(colorTexture, varTexcoord + (vec2(-1.0, +1.0) * texcoordOffset)).xyz; vec3 rgbSE = texture2D(colorTexture, varTexcoord + (vec2(+1.0, +1.0) * texcoordOffset)).xyz; vec3 rgbM = texture2D(colorTexture, varTexcoord).xyz; + // convert RGB values to luminance vec3 luma = vec3(0.299, 0.587, 0.114); float lumaNW = dot(rgbNW, luma); float lumaNE = dot(rgbNE, luma); @@ -46,20 +58,23 @@ void main() { float lumaSE = dot(rgbSE, luma); float lumaM = dot( rgbM, luma); + // luma range of local neighborhood float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + // direction perpendicular to local luma gradient vec2 dir; dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); - + + // compute clamped direction offset for additional "two-tap" samples + // longer vector = blurry, shorter vector = sharp float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); - float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce); - dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset; + // perform additional texture sampling perpendicular to gradient vec3 rgbA = (1.0/2.0) * ( texture2D(colorTexture, varTexcoord + dir * (1.0/3.0 - 0.5)).xyz + texture2D(colorTexture, varTexcoord + dir * (2.0/3.0 - 0.5)).xyz); @@ -68,6 +83,8 @@ void main() { texture2D(colorTexture, varTexcoord + dir * (3.0/3.0 - 0.5)).xyz); float lumaB = dot(rgbB, luma); + // compare luma of new samples to the luma range of the original neighborhood + // if the new samples exceed this range, just use the first two samples instead of all four if (lumaB < lumaMin || lumaB > lumaMax) { outFragColor.xyz=rgbA; } else { From 2d95d1c236f5d2dc00a2811ccc2b052ba6b67e76 Mon Sep 17 00:00:00 2001 From: Raffi Bedikian Date: Wed, 2 Sep 2015 13:49:21 -0700 Subject: [PATCH 05/12] Fix various whitespace issues --- .../render-utils/src/AntialiasingEffect.cpp | 170 +++++++++--------- .../render-utils/src/AntialiasingEffect.h | 22 +-- libraries/render-utils/src/fxaa.slf | 18 +- 3 files changed, 105 insertions(+), 105 deletions(-) diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 4e1b6f451c..283561fc57 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -33,119 +33,119 @@ Antialiasing::Antialiasing() { } const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { - if (!_antialiasingPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + if (!_antialiasingPipeline) { + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); + auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); - gpu::Shader::makeProgram(*program, slotBindings); + gpu::Shader::makeProgram(*program, slotBindings); - _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset"); + _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset"); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(false, false, gpu::LESS_EQUAL); + state->setDepthTest(false, false, gpu::LESS_EQUAL); - // Link the antialiasing FBO to texture - _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, - DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height())); - auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); - auto width = _antialiasingBuffer->getWidth(); - auto height = _antialiasingBuffer->getHeight(); - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); + // Link the antialiasing FBO to texture + _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, + DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height())); + auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); + auto width = _antialiasingBuffer->getWidth(); + auto height = _antialiasingBuffer->getHeight(); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); - // Good to go add the brand new pipeline - _antialiasingPipeline.reset(gpu::Pipeline::create(program, state)); - } - return _antialiasingPipeline; + // Good to go add the brand new pipeline + _antialiasingPipeline.reset(gpu::Pipeline::create(program, state)); + } + return _antialiasingPipeline; } const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { - if (!_blendPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_blend_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + if (!_blendPipeline) { + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); + auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_blend_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); - gpu::Shader::makeProgram(*program, slotBindings); + gpu::Shader::makeProgram(*program, slotBindings); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(false, false, gpu::LESS_EQUAL); + state->setDepthTest(false, false, gpu::LESS_EQUAL); - // Good to go add the brand new pipeline - _blendPipeline.reset(gpu::Pipeline::create(program, state)); - } - return _blendPipeline; + // Good to go add the brand new pipeline + _blendPipeline.reset(gpu::Pipeline::create(program, state)); + } + return _blendPipeline; } void Antialiasing::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { - assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args); + assert(renderContext->args->_viewFrustum); - gpu::Batch batch; - 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; + 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; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - batch.setModelTransform(Transform()); + glm::mat4 projMat; + Transform viewMat; + args->_viewFrustum->evalProjectionMatrix(projMat); + args->_viewFrustum->evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + batch.setModelTransform(Transform()); - // FXAA step - getAntialiasingPipeline(); - batch.setResourceTexture(0, framebufferCache->getPrimaryColorTexture()); - _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); - batch.setFramebuffer(_antialiasingBuffer); - batch.setPipeline(getAntialiasingPipeline()); + // FXAA step + getAntialiasingPipeline(); + batch.setResourceTexture(0, framebufferCache->getPrimaryColorTexture()); + _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); + batch.setFramebuffer(_antialiasingBuffer); + batch.setPipeline(getAntialiasingPipeline()); - // initialize the view-space unpacking uniforms using frustum data - float left, right, bottom, top, nearVal, farVal; - glm::vec4 nearClipPlane, farClipPlane; + // 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); + args->_viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - float depthScale = (farVal - nearVal) / farVal; - 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; + float depthScale = (farVal - nearVal) / farVal; + 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(_texcoordOffsetLoc, 1.0 / fbWidth, 1.0 / fbHeight); + batch._glUniform2f(_texcoordOffsetLoc, 1.0 / fbWidth, 1.0 / fbHeight); - glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); - glm::vec2 bottomLeft(-1.0f, -1.0f); - glm::vec2 topRight(1.0f, 1.0f); - glm::vec2 texCoordTopLeft(0.0f, 0.0f); - glm::vec2 texCoordBottomRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); + glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); + glm::vec2 bottomLeft(-1.0f, -1.0f); + glm::vec2 topRight(1.0f, 1.0f); + glm::vec2 texCoordTopLeft(0.0f, 0.0f); + glm::vec2 texCoordBottomRight(1.0f, 1.0f); + DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); - // Blend step - getBlendPipeline(); - batch.setResourceTexture(0, _antialiasingTexture); - batch.setFramebuffer(framebufferCache->getPrimaryFramebuffer()); - batch.setPipeline(getBlendPipeline()); + // Blend step + getBlendPipeline(); + batch.setResourceTexture(0, _antialiasingTexture); + batch.setFramebuffer(framebufferCache->getPrimaryFramebuffer()); + batch.setPipeline(getBlendPipeline()); - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); + DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); - // Ready to render - args->_context->render((batch)); + // Ready to render + args->_context->render((batch)); } diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 96cf3aaf3e..c7cce4cb15 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -19,25 +19,25 @@ class Antialiasing { public: - Antialiasing(); + Antialiasing(); - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); - typedef render::Job::Model JobModel; + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + typedef render::Job::Model JobModel; - const gpu::PipelinePointer& getAntialiasingPipeline(); - const gpu::PipelinePointer& getBlendPipeline(); + const gpu::PipelinePointer& getAntialiasingPipeline(); + const gpu::PipelinePointer& getBlendPipeline(); private: - // Uniforms for AA - gpu::int32 _texcoordOffsetLoc; + // Uniforms for AA + gpu::int32 _texcoordOffsetLoc; - gpu::FramebufferPointer _antialiasingBuffer; + gpu::FramebufferPointer _antialiasingBuffer; - gpu::TexturePointer _antialiasingTexture; + gpu::TexturePointer _antialiasingTexture; - gpu::PipelinePointer _antialiasingPipeline; - gpu::PipelinePointer _blendPipeline; + gpu::PipelinePointer _antialiasingPipeline; + gpu::PipelinePointer _blendPipeline; }; diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 69596e13ce..d1c50b2c58 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -35,11 +35,11 @@ void main() { // local contrast multiplier for performing AA // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail // see "fxaaQualityEdgeThreshold" - float FXAA_REDUCE_MUL = 1.0/8.0; + float FXAA_REDUCE_MUL = 1.0 / 8.0; // luminance threshold for processing dark colors // see "fxaaQualityEdgeThresholdMin" - float FXAA_REDUCE_MIN = (1.0/128.0); + float FXAA_REDUCE_MIN = 1.0 / 128.0; // fetch raw RGB values for nearby locations // sampling pattern is "five on a die" (each diagonal direction and the center) @@ -70,17 +70,17 @@ void main() { // compute clamped direction offset for additional "two-tap" samples // longer vector = blurry, shorter vector = sharp float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); - float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset; // perform additional texture sampling perpendicular to gradient - vec3 rgbA = (1.0/2.0) * ( - texture2D(colorTexture, varTexcoord + dir * (1.0/3.0 - 0.5)).xyz + - texture2D(colorTexture, varTexcoord + dir * (2.0/3.0 - 0.5)).xyz); - vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * ( - texture2D(colorTexture, varTexcoord + dir * (0.0/3.0 - 0.5)).xyz + - texture2D(colorTexture, varTexcoord + dir * (3.0/3.0 - 0.5)).xyz); + vec3 rgbA = (1.0 / 2.0) * ( + texture2D(colorTexture, varTexcoord + dir * (1.0 / 3.0 - 0.5)).xyz + + texture2D(colorTexture, varTexcoord + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * ( + texture2D(colorTexture, varTexcoord + dir * (0.0 / 3.0 - 0.5)).xyz + + texture2D(colorTexture, varTexcoord + dir * (3.0 / 3.0 - 0.5)).xyz); float lumaB = dot(rgbB, luma); // compare luma of new samples to the luma range of the original neighborhood From d53c1d0fba073744d8a976f59b05ebd4f222591e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 3 Sep 2015 13:22:04 -0700 Subject: [PATCH 06/12] Change default avatar collision sound. Note that this only used when Interface.ini does not already have a setting, and the empty string is a valid setting. To reset to default, either delete Interface.ini or the line in it that begins with collisionSoundURL=, or just set Interface|Edit->Preferences->Avatar collision sound URL to https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6939720f32..be984a8053 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -68,7 +68,7 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; const int SCRIPTED_MOTOR_CAMERA_FRAME = 0; const int SCRIPTED_MOTOR_AVATAR_FRAME = 1; const int SCRIPTED_MOTOR_WORLD_FRAME = 2; -const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav"; +const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav"; const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; From 063320771ca7d19b916f8ce373d4bf3f01c5d0fa Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 3 Sep 2015 16:37:24 -0700 Subject: [PATCH 07/12] fix a bug in entity script includes and file based includes on windows --- libraries/script-engine/src/BatchLoader.cpp | 4 ---- libraries/shared/src/RegisteredMetaTypes.cpp | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/libraries/script-engine/src/BatchLoader.cpp b/libraries/script-engine/src/BatchLoader.cpp index db0743808f..e6115324c9 100644 --- a/libraries/script-engine/src/BatchLoader.cpp +++ b/libraries/script-engine/src/BatchLoader.cpp @@ -58,11 +58,7 @@ void BatchLoader::start() { connect(this, &QObject::destroyed, reply, &QObject::deleteLater); } else { -#ifdef _WIN32 - QString fileName = url.toString(); -#else QString fileName = url.toLocalFile(); -#endif qCDebug(scriptengine) << "Reading file at " << fileName; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 2c4b213fcb..b2389f4db6 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -26,8 +26,7 @@ static int quatMetaTypeId = qRegisterMetaType(); static int xColorMetaTypeId = qRegisterMetaType(); static int pickRayMetaTypeId = qRegisterMetaType(); static int collisionMetaTypeId = qRegisterMetaType(); - - +static int qMapURLStringMetaTypeId = qRegisterMetaType>(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue); From b0f239b89dca2799f7d3707caf3133877c56716a Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 3 Sep 2015 19:06:26 -0700 Subject: [PATCH 08/12] also make sure local file includes work for non-entity scripts --- libraries/script-engine/src/BatchLoader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/script-engine/src/BatchLoader.cpp b/libraries/script-engine/src/BatchLoader.cpp index e6115324c9..ac0cafa6a9 100644 --- a/libraries/script-engine/src/BatchLoader.cpp +++ b/libraries/script-engine/src/BatchLoader.cpp @@ -60,6 +60,12 @@ void BatchLoader::start() { } else { QString fileName = url.toLocalFile(); + // sometimes on windows, we see the toLocalFile() return null, + // in this case we will attempt to simply use the url as a string + if (fileName.isEmpty()) { + fileName = url.toString(); + } + qCDebug(scriptengine) << "Reading file at " << fileName; QFile scriptFile(fileName); From c8069ba73ce3ee80cf63d96cf4c49dc4dde38b95 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 2 Sep 2015 22:37:09 -0700 Subject: [PATCH 09/12] Working on magballs polish --- examples/defaultScripts.js | 1 + examples/html/magBalls/addMode.html | 25 ++ examples/html/magBalls/deleteMode.html | 25 ++ examples/html/magBalls/magBalls.css | 14 + examples/html/magBalls/moveMode.html | 25 ++ examples/libraries/avatarRelativeOverlays.js | 55 ++++ examples/libraries/constants.js | 2 + examples/libraries/highlighter.js | 25 +- examples/libraries/omniTool.js | 73 ++++- .../omniTool/models/invisibleWand.js | 6 + .../libraries/omniTool/models/modelBase.js | 6 + examples/libraries/omniTool/models/wand.js | 34 +-- examples/libraries/omniTool/modules/test.js | 31 +- examples/libraries/utils.js | 19 ++ examples/toys/magBalls.js | 287 +++++++++++++++--- examples/toys/magBalls/constants.js | 25 +- examples/toys/magBalls/debugUtils.js | 95 ------ examples/toys/magBalls/edgeSpring.js | 5 +- examples/toys/magBalls/magBalls.js | 125 +++++++- interface/src/ui/overlays/Web3DOverlay.cpp | 7 +- libraries/render-utils/src/simple.slv | 2 + 21 files changed, 681 insertions(+), 206 deletions(-) create mode 100644 examples/html/magBalls/addMode.html create mode 100644 examples/html/magBalls/deleteMode.html create mode 100644 examples/html/magBalls/magBalls.css create mode 100644 examples/html/magBalls/moveMode.html create mode 100644 examples/libraries/avatarRelativeOverlays.js create mode 100644 examples/libraries/omniTool/models/invisibleWand.js diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index c602c36382..44801ef737 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -17,3 +17,4 @@ Script.load("users.js"); Script.load("grab.js"); Script.load("directory.js"); Script.load("dialTone.js"); +Script.load("libraries/omniTool.js"); diff --git a/examples/html/magBalls/addMode.html b/examples/html/magBalls/addMode.html new file mode 100644 index 0000000000..f1db142daa --- /dev/null +++ b/examples/html/magBalls/addMode.html @@ -0,0 +1,25 @@ + + + + + + + +
Add
+ + \ No newline at end of file diff --git a/examples/html/magBalls/deleteMode.html b/examples/html/magBalls/deleteMode.html new file mode 100644 index 0000000000..94f23fdef8 --- /dev/null +++ b/examples/html/magBalls/deleteMode.html @@ -0,0 +1,25 @@ + + + + + + + +
Delete
+ + \ No newline at end of file diff --git a/examples/html/magBalls/magBalls.css b/examples/html/magBalls/magBalls.css new file mode 100644 index 0000000000..339f094ea7 --- /dev/null +++ b/examples/html/magBalls/magBalls.css @@ -0,0 +1,14 @@ +.container { + display:table; + position:absolute; + width: 100%; + height: 100%; +} + +span { + font-size: 25vw; + display: table-cell; + vertical-align: middle; + line-height: normal; + text-align: center; +} \ No newline at end of file diff --git a/examples/html/magBalls/moveMode.html b/examples/html/magBalls/moveMode.html new file mode 100644 index 0000000000..eae53be635 --- /dev/null +++ b/examples/html/magBalls/moveMode.html @@ -0,0 +1,25 @@ + + + + + + + +
Move
+ + \ No newline at end of file diff --git a/examples/libraries/avatarRelativeOverlays.js b/examples/libraries/avatarRelativeOverlays.js new file mode 100644 index 0000000000..b12971037c --- /dev/null +++ b/examples/libraries/avatarRelativeOverlays.js @@ -0,0 +1,55 @@ + +AvatarRelativeOverlays = function() { + // id -> position & rotation + this.overlays = {}; + this.lastAvatarTransform = { + position: ZERO_VECTOR, + rotation: IDENTITY_QUATERNION, + }; +} + +// FIXME judder in movement is annoying.... add an option to +// automatically hide all overlays when the position or orientation change and then +// restore the ones that were previously visible once the movement stops. +AvatarRelativeOverlays.prototype.onUpdate = function(deltaTime) { + // cache avatar position and orientation and only update on change + if (Vec3.equal(this.lastAvatarTransform.position, MyAvatar.position) && + Quat.equal(this.lastAvatarTransform.rotation, MyAvatar.orientation)) { + return; + } + + this.lastAvatarTransform.position = MyAvatar.position; + this.lastAvatarTransform.rotation = MyAvatar.orientation; + for (var overlayId in this.overlays) { + this.updateOverlayTransform(overlayId); + } +} + +AvatarRelativeOverlays.prototype.updateOverlayTransform = function(overlayId) { + Overlays.editOverlay(overlayId, { + position: getEyeRelativePosition(this.overlays[overlayId].position), + rotation: getAvatarRelativeRotation(this.overlays[overlayId].rotation), + }) +} + +AvatarRelativeOverlays.prototype.addOverlay = function(type, overlayDefinition) { + var overlayId = Overlays.addOverlay(type, overlayDefinition); + if (!overlayId) { + logDebug("Failed to create overlay of type " + type); + return; + } + this.overlays[overlayId] = { + position: overlayDefinition.position || ZERO_VECTOR, + rotation: overlayDefinition.rotation || IDENTITY_QUATERNION, + }; + this.updateOverlayTransform(overlayId); + return overlayId; +} + +AvatarRelativeOverlays.prototype.deleteAll = function() { + for (var overlayId in this.overlays) { + Overlays.deleteOverlay(overlayId); + } + this.overlays = {}; +} + diff --git a/examples/libraries/constants.js b/examples/libraries/constants.js index 3ed7c02633..117e72267c 100644 --- a/examples/libraries/constants.js +++ b/examples/libraries/constants.js @@ -11,6 +11,8 @@ STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx"; ZERO_VECTOR = { x: 0, y: 0, z: 0 }; +IDENTITY_QUATERNION = { w: 1, x: 0, y: 0, z: 0 }; + COLORS = { WHITE: { red: 255, diff --git a/examples/libraries/highlighter.js b/examples/libraries/highlighter.js index b3550b6c8a..33aa445b88 100644 --- a/examples/libraries/highlighter.js +++ b/examples/libraries/highlighter.js @@ -29,7 +29,7 @@ var SELECTION_OVERLAY = { Highlighter = function() { this.highlightCube = Overlays.addOverlay("cube", this.SELECTION_OVERLAY); - this.hightlighted = null; + this.highlighted = null; var _this = this; Script.scriptEnding.connect(function() { _this.onCleanup(); @@ -40,9 +40,9 @@ Highlighter.prototype.onCleanup = function() { Overlays.deleteOverlay(this.highlightCube); } -Highlighter.prototype.highlight = function(entityId) { - if (entityId != this.hightlighted) { - this.hightlighted = entityId; +Highlighter.prototype.highlight = function(entityIdOrPosition) { + if (entityIdOrPosition != this.highlighted) { + this.highlighted = entityIdOrPosition; this.updateHighlight(); } } @@ -53,6 +53,13 @@ Highlighter.prototype.setSize = function(newSize) { }); } +Highlighter.prototype.setColor = function(color) { + Overlays.editOverlay(this.highlightCube, { + color: color + }); +} + + Highlighter.prototype.setRotation = function(newRotation) { Overlays.editOverlay(this.highlightCube, { rotation: newRotation @@ -60,11 +67,15 @@ Highlighter.prototype.setRotation = function(newRotation) { } Highlighter.prototype.updateHighlight = function() { - if (this.hightlighted) { - var properties = Entities.getEntityProperties(this.hightlighted); + if (this.highlighted) { + var position = this.highlighted; + if (typeof this.highlighted === "string") { + var properties = Entities.getEntityProperties(this.highlighted); + position = properties.position; + } // logDebug("Making highlight " + this.highlightCube + " visible @ " + vec3toStr(properties.position)); Overlays.editOverlay(this.highlightCube, { - position: properties.position, + position: position, visible: true }); } else { diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js index 724f30c548..26c299cdfb 100644 --- a/examples/libraries/omniTool.js +++ b/examples/libraries/omniTool.js @@ -11,13 +11,14 @@ Script.include("utils.js"); Script.include("highlighter.js"); Script.include("omniTool/models/modelBase.js"); Script.include("omniTool/models/wand.js"); +Script.include("omniTool/models/invisibleWand.js"); OmniToolModules = {}; OmniToolModuleType = null; OmniTool = function(side) { this.OMNI_KEY = "OmniTool"; - this.MAX_FRAMERATE = 30; + this.MAX_FRAMERATE = 60; this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE this.SIDE = side; this.PALM = 2 * side; @@ -28,6 +29,7 @@ OmniTool = function(side) { this.ignoreEntities = {}; this.nearestOmniEntity = { id: null, + inside: false, position: null, distance: Infinity, @@ -38,13 +40,11 @@ OmniTool = function(side) { this.activeOmniEntityId = null; this.lastUpdateInterval = 0; - this.tipLength = 0.4; this.active = false; this.module = null; this.moduleEntityId = null; this.lastScanPosition = ZERO_VECTOR; - this.model = new Wand(); - this.model.setLength(this.tipLength); + this.showWand(false); // Connect to desired events var _this = this; @@ -65,6 +65,23 @@ OmniTool = function(side) { }); } +OmniTool.prototype.showWand = function(show) { + if (this.model && this.model.onCleanup) { + this.model.onCleanup(); + } + logDebug("Showing wand: " + show); + if (show) { + this.model = new Wand(); + this.model.setLength(0.4); + this.model.setVisible(true); + } else { + this.model = new InvisibleWand(); + this.model.setLength(0.1); + this.model.setVisible(true); + } +} + + OmniTool.prototype.onCleanup = function(action) { this.unloadModule(); } @@ -110,10 +127,9 @@ OmniTool.prototype.setActive = function(active) { if (active === this.active) { return; } - logDebug("omnitool changing active state: " + active); + logDebug("OmniTool changing active state: " + active); this.active = active; this.model.setVisible(this.active); - if (this.module && this.module.onActiveChanged) { this.module.onActiveChanged(this.side); } @@ -125,14 +141,25 @@ OmniTool.prototype.onUpdate = function(deltaTime) { this.position = Controller.getSpatialControlPosition(this.PALM); // When on the base, hydras report a position of 0 this.setActive(Vec3.length(this.position) > 0.001); + if (!this.active) { + return; + } + + + if (this.model) { + // Update the wand + var rawRotation = Controller.getSpatialControlRawRotation(this.PALM); + this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation); + this.model.setTransform({ + rotation: this.rotation, + position: this.position, + }); + + if (this.model.onUpdate) { + this.model.onUpdate(deltaTime); + } + } - var rawRotation = Controller.getSpatialControlRawRotation(this.PALM); - this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation); - - this.model.setTransform({ - rotation: this.rotation, - position: this.position, - }); this.scan(); @@ -144,6 +171,19 @@ OmniTool.prototype.onUpdate = function(deltaTime) { OmniTool.prototype.onClick = function() { // First check to see if the user is switching to a new omni module if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) { + + // If this is already the active entity, turn it off + // FIXME add a flag to allow omni modules to cause this entity to be + // ignored in order to support items that will be picked up. + if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) { + this.showWand(false); + this.unloadModule(); + this.highlighter.setColor("White"); + return; + } + + this.showWand(true); + this.highlighter.setColor("Red"); this.activateNewOmniModule(); return; } @@ -157,12 +197,10 @@ OmniTool.prototype.onClick = function() { } OmniTool.prototype.onRelease = function() { - // FIXME how to I switch to a new module? if (this.module && this.module.onRelease) { this.module.onRelease(); return; } - logDebug("Base omnitool does nothing on release"); } // FIXME resturn a structure of all nearby entities to distances @@ -210,6 +248,11 @@ OmniTool.prototype.getPosition = function() { OmniTool.prototype.onEnterNearestOmniEntity = function() { this.nearestOmniEntity.inside = true; this.highlighter.highlight(this.nearestOmniEntity.id); + if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) { + this.highlighter.setColor("Red"); + } else { + this.highlighter.setColor("White"); + } logDebug("On enter omniEntity " + this.nearestOmniEntity.id); } diff --git a/examples/libraries/omniTool/models/invisibleWand.js b/examples/libraries/omniTool/models/invisibleWand.js new file mode 100644 index 0000000000..63e0945af3 --- /dev/null +++ b/examples/libraries/omniTool/models/invisibleWand.js @@ -0,0 +1,6 @@ + +InvisibleWand = function() { +} + +InvisibleWand.prototype = Object.create( ModelBase.prototype ); + diff --git a/examples/libraries/omniTool/models/modelBase.js b/examples/libraries/omniTool/models/modelBase.js index 7697856d3f..87f93b1204 100644 --- a/examples/libraries/omniTool/models/modelBase.js +++ b/examples/libraries/omniTool/models/modelBase.js @@ -17,3 +17,9 @@ ModelBase.prototype.setTransform = function(transform) { this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 }); this.tipPosition = Vec3.sum(this.position, this.tipVector); } + +ModelBase.prototype.setTipColors = function(color1, color2) { +} + +ModelBase.prototype.onCleanup = function() { +} diff --git a/examples/libraries/omniTool/models/wand.js b/examples/libraries/omniTool/models/wand.js index 8f0fe92b53..af28afb81c 100644 --- a/examples/libraries/omniTool/models/wand.js +++ b/examples/libraries/omniTool/models/wand.js @@ -1,22 +1,18 @@ Wand = function() { // Max updates fps - this.MAX_FRAMERATE = 30 - this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE this.DEFAULT_TIP_COLORS = [ { red: 128, green: 128, blue: 128, }, { - red: 64, - green: 64, - blue: 64, + red: 0, + green: 0, + blue: 0, }]; this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45); - // FIXME does this need to be a member of this? - this.lastUpdateInterval = 0; - + this.pointers = [ Overlays.addOverlay("cube", { position: ZERO_VECTOR, @@ -45,17 +41,7 @@ Wand = function() { var _this = this; Script.scriptEnding.connect(function() { - Overlays.deleteOverlay(_this.pointers[0]); - Overlays.deleteOverlay(_this.pointers[1]); - Overlays.deleteOverlay(_this.wand); - }); - - Script.update.connect(function(deltaTime) { - _this.lastUpdateInterval += deltaTime; - if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) { - _this.onUpdate(_this.lastUpdateInterval); - _this.lastUpdateInterval = 0; - } + _this.onCleanup(); }); } @@ -76,7 +62,6 @@ Wand.prototype.setVisible = function(visible) { Wand.prototype.setTransform = function(transform) { ModelBase.prototype.setTransform.call(this, transform); - var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector)); Overlays.editOverlay(this.pointers[0], { position: this.tipPosition, @@ -106,7 +91,10 @@ Wand.prototype.setTipColors = function(color1, color2) { } Wand.prototype.onUpdate = function(deltaTime) { + logDebug("Z4"); + if (this.visible) { + logDebug("5"); var time = new Date().getTime() / 250; var scale1 = Math.abs(Math.sin(time)); var scale2 = Math.abs(Math.cos(time)); @@ -118,3 +106,9 @@ Wand.prototype.onUpdate = function(deltaTime) { }); } } + +Wand.prototype.onCleanup = function() { + Overlays.deleteOverlay(this.pointers[0]); + Overlays.deleteOverlay(this.pointers[1]); + Overlays.deleteOverlay(this.wand); +} \ No newline at end of file diff --git a/examples/libraries/omniTool/modules/test.js b/examples/libraries/omniTool/modules/test.js index 1ca806affa..9f7191b2d0 100644 --- a/examples/libraries/omniTool/modules/test.js +++ b/examples/libraries/omniTool/modules/test.js @@ -1,9 +1,36 @@ -OmniToolModules.Test = function() { +Script.include("avatarRelativeOverlays.js"); + +OmniToolModules.Test = function(omniTool, activeEntityId) { + this.omniTool = omniTool; + this.activeEntityId = activeEntityId; + this.avatarOverlays = new AvatarRelativeOverlays(); } +OmniToolModules.Test.prototype.onUnload = function() { + if (this.testOverlay) { + Overlays.deleteOverlay(this.testOverlay); + this.testOverlay = 0; + } +} + +var CUBE_POSITION = { + x: 0.1, + y: -0.1, + z: -0.4 +}; + OmniToolModules.Test.prototype.onClick = function() { - logDebug("Test module onClick"); + if (this.testOverlay) { + Overlays.deleteOverlay(this.testOverlay); + this.testOverlay = 0; + } } + +OmniToolModules.Test.prototype.onUpdate = function(deltaTime) { + this.avatarOverlays.onUpdate(deltaTime); +} + + OmniToolModuleType = "Test" \ No newline at end of file diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index 1c2816e7a0..c6143a51a8 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -11,6 +11,15 @@ vec3toStr = function (v, digits) { return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }"; } + +colorMix = function(colorA, colorB, mix) { + var result = {}; + for (var key in colorA) { + result[key] = (colorA[key] * (1 - mix)) + (colorB[key] * mix); + } + return result; +} + scaleLine = function (start, end, scale) { var v = Vec3.subtract(end, start); var length = Vec3.length(v); @@ -127,3 +136,13 @@ findSpherePointHit = function(sphereCenter, sphereRadius, point) { findSphereSphereHit = function(firstCenter, firstRadius, secondCenter, secondRadius) { return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter); } + +// Given a vec3 v, return a vec3 that is the same vector relative to the avatars +// DEFAULT eye position, rotated into the avatars reference frame. +getEyeRelativePosition = function(v) { + return Vec3.sum(MyAvatar.getDefaultEyePosition(), Vec3.multiplyQbyV(MyAvatar.orientation, v)); +} + +getAvatarRelativeRotation = function(q) { + return Quat.multiply(MyAvatar.orientation, q); +} diff --git a/examples/toys/magBalls.js b/examples/toys/magBalls.js index 8e441901a2..7c11a94e06 100644 --- a/examples/toys/magBalls.js +++ b/examples/toys/magBalls.js @@ -11,21 +11,10 @@ Script.include("../toys/magBalls/constants.js"); Script.include("../toys/magBalls/graph.js"); Script.include("../toys/magBalls/edgeSpring.js"); Script.include("../toys/magBalls/magBalls.js"); +Script.include("avatarRelativeOverlays.js"); OmniToolModuleType = "MagBallsController" - -OmniToolModules.MagBallsController = function(omniTool, entityId) { - this.omniTool = omniTool; - this.entityId = entityId; - this.highlighter = new Highlighter(); - this.magBalls = new MagBalls(); - this.highlighter.setSize(BALL_SIZE); - this.ghostEdges = {}; -} - -var MAG_BALLS_DATA_NAME = "magBalls"; - getMagBallsData = function(id) { return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {}); } @@ -34,54 +23,177 @@ setMagBallsData = function(id, value) { setEntityCustomData(MAG_BALLS_DATA_NAME, id, value); } -//var magBalls = new MagBalls(); -// DEBUGGING ONLY - Clear any previous balls -// magBalls.clear(); +var UI_BALL_RADIUS = 0.01; +var MODE_INFO = { }; -OmniToolModules.MagBallsController.prototype.onClick = function() { - logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition)); +MODE_INFO[BALL_EDIT_MODE_ADD] = { + uiPosition: { + x: 0.15, + y: -0.08, + z: -0.35, + }, + colors: [ COLORS.GREEN, COLORS.BLUE ], + // FIXME use an http path or find a way to get the relative path to the file + url: "file:///" + Script.resolvePath('../html/magBalls/addMode.html').replace("c:", "C:"), +}; + +MODE_INFO[BALL_EDIT_MODE_DELETE] = { + uiPosition: { + x: 0.20, + y: -0.08, + z: -0.32, + }, + colors: [ COLORS.RED, COLORS.BLUE ], + // FIXME use an http path or find a way to get the relative path to the file + url: "file:///" + Script.resolvePath('../html/magBalls/deleteMode.html').replace("c:", "C:"), +}; + + +var UI_POSITION_MODE_LABEL = Vec3.multiply(0.5, + Vec3.sum(MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition, + MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition)); + +UI_POSITION_MODE_LABEL.y = -0.02; + +var UI_BALL_PROTOTYPE = { + size: UI_BALL_RADIUS * 2.0, + alpha: 1.0, + solid: true, + visible: true, +} + +OmniToolModules.MagBallsController = function(omniTool, entityId) { + this.omniTool = omniTool; + this.entityId = entityId; + + // In hold mode, holding a ball requires that you keep the action + // button pressed, while if this is false, clicking on a ball selects + // it and clicking again will drop it. + this.holdMode = true; + + this.highlighter = new Highlighter(); + this.magBalls = new MagBalls(); + this.highlighter.setSize(BALL_SIZE); + this.ghostEdges = {}; + this.selectionRadiusMultipler = 1.5; + this.uiOverlays = new AvatarRelativeOverlays(); - this.selected = this.highlighter.hightlighted; - logDebug("This selected: " + this.selected); - if (!this.selected) { - this.selected = this.magBalls.createBall(this.tipPosition); - } - this.magBalls.selectBall(this.selected); - this.highlighter.highlight(null); - logDebug("Selected " + this.selected); + + // create the overlay relative to the avatar + this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, { + color: MODE_INFO[BALL_EDIT_MODE_ADD].colors[0], + position: MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition, + })); + this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, { + color: MODE_INFO[BALL_EDIT_MODE_DELETE].colors[0], + position: MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition, + })); + + // FIXME find the proper URLs to use + this.modeLabel = this.uiOverlays.addOverlay("web3d", { + isFacingAvatar: true, + alpha: 1.0, + dimensions: { x: 0.16, y: 0.12, z: 0.001}, + color: "White", + position: UI_POSITION_MODE_LABEL, + }); + + this.setMode(BALL_EDIT_MODE_ADD); + + // DEBUGGING ONLY - Fix old, bad edge bounding boxes + //for (var edgeId in this.magBalls.edges) { + // Entities.editEntity(edgeId, { + // dimensions: LINE_DIMENSIONS, + // }); + //} + // DEBUGGING ONLY - Clear any previous balls + // this.magBalls.clear(); + // DEBUGGING ONLY - Attempt to fix connections between balls + // and delete bad connections. Warning... if you haven't looked around + // and caused the domain server to send you all the nearby balls as well as the connections, + // this can break your structures + // this.magBalls.repair(); } -OmniToolModules.MagBallsController.prototype.onRelease = function() { - logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition)); +OmniToolModules.MagBallsController.prototype.onUnload = function() { this.clearGhostEdges(); - if (this.selected) { - this.magBalls.releaseBall(this.selected); - this.selected = null; - } + this.uiOverlays.deleteAll(); } -OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) { - this.tipPosition = this.omniTool.getPosition(); - if (!this.selected) { - // Find the highlight target and set it. - var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(target); - if (!target) { - this.magBalls.onUpdate(deltaTime); + +OmniToolModules.MagBallsController.prototype.setMode = function(mode) { + if (mode === this.mode) { + return; + } + + logDebug("Changing mode to '" + mode + "'"); + Overlays.editOverlay(this.modeLabel, { + url: MODE_INFO[mode].url + }); + + this.mode = mode; + var color1; + var color2; + switch (this.mode) { + case BALL_EDIT_MODE_ADD: + color1 = COLORS.BLUE; + color2 = COLORS.GREEN; + break; + + case BALL_EDIT_MODE_MOVE: + color1 = COLORS.GREEN; + color2 = COLORS.LIGHT_GREEN; + break; + + case BALL_EDIT_MODE_DELETE: + color1 = COLORS.RED; + color2 = COLORS.BLUE; + break; + + case BALL_EDIT_MODE_DELETE_SHAPE: + color1 = COLORS.RED; + color2 = COLORS.YELLOW; + break; + } + this.omniTool.model.setTipColors(color1, color2); + +} + +OmniToolModules.MagBallsController.prototype.findUiBallHit = function() { + var result = null; + for (var mode in MODE_INFO) { + var modeInfo = MODE_INFO[mode]; + var spherePoint = getEyeRelativePosition(modeInfo.uiPosition); + if (findSpherePointHit(spherePoint, UI_BALL_RADIUS * 2, this.tipPosition)) { + this.highlighter.highlight(spherePoint); + this.highlighter.setColor("White"); + // FIXME why doesn't this work? + this.highlighter.setSize(UI_BALL_RADIUS * 4); + return mode; } + } + return; +} + +OmniToolModules.MagBallsController.prototype.onUpdateSelected = function(deltaTime) { + if (!this.selected) { return; } - this.highlighter.highlight(null); Entities.editEntity(this.selected, { position: this.tipPosition }); var targetBalls = this.magBalls.findPotentialEdges(this.selected); for (var ballId in targetBalls) { + var targetPosition = this.magBalls.getNodePosition(ballId); + var distance = Vec3.distance(targetPosition, this.tipPosition); + var variance = this.magBalls.getVariance(distance); + var mix = Math.abs(variance) / this.magBalls.MAX_VARIANCE; + var color = colorMix(COLORS.YELLOW, COLORS.RED, mix); if (!this.ghostEdges[ballId]) { // create the ovleray this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { start: this.magBalls.getNodePosition(ballId), end: this.tipPosition, - color: COLORS.RED, + color: color, alpha: 1, lineWidth: 5, visible: true, @@ -89,6 +201,7 @@ OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) { } else { Overlays.editOverlay(this.ghostEdges[ballId], { end: this.tipPosition, + color: color, }); } } @@ -100,6 +213,95 @@ OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) { } } +OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) { + this.tipPosition = this.omniTool.getPosition(); + this.uiOverlays.onUpdate(deltaTime); + + this.onUpdateSelected(); + + if (this.findUiBallHit()) { + return; + } + + if (!this.selected) { + // Find the highlight target and set it. + var target = this.magBalls.findNearestNode(this.tipPosition, BALL_RADIUS * this.selectionRadiusMultipler); + this.highlighter.highlight(target); + this.highlighter.setColor(MODE_INFO[this.mode].colors[0]); + if (!target) { + this.magBalls.onUpdate(deltaTime); + } + return; + } +} + +OmniToolModules.MagBallsController.prototype.deselect = function() { + if (!this.selected) { + return false + } + this.clearGhostEdges(); + this.magBalls.releaseBall(this.selected); + this.selected = null; + return true; +} + + +OmniToolModules.MagBallsController.prototype.onClick = function() { + var newMode = this.findUiBallHit(); + if (newMode) { + if (this.selected) { + this.magBalls.destroyNode(highlighted); + this.selected = null; + } + this.setMode(newMode); + return; + } + + if (this.deselect()) { + return; + } + + logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition)); + + // TODO add checking against UI shapes for adding or deleting balls. + var highlighted = this.highlighter.highlighted; + if (this.mode == BALL_EDIT_MODE_ADD && !highlighted) { + highlighted = this.magBalls.createBall(this.tipPosition); + } + + // Nothing to select or create means we're done here. + if (!highlighted) { + return; + } + + switch (this.mode) { + case BALL_EDIT_MODE_ADD: + case BALL_EDIT_MODE_MOVE: + this.magBalls.selectBall(highlighted); + this.selected = highlighted; + logDebug("Selected " + this.selected); + break; + + case BALL_EDIT_MODE_DELETE: + this.magBalls.destroyNode(highlighted); + break; + + case BALL_EDIT_MODE_DELETE_SHAPE: + logDebug("Not implemented yet"); + break; + } + + if (this.selected) { + this.highlighter.highlight(null); + } +} + +OmniToolModules.MagBallsController.prototype.onRelease = function() { + if (this.holdMode) { + this.deselect(); + } +} + OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() { for(var ballId in this.ghostEdges) { Overlays.deleteOverlay(this.ghostEdges[ballId]); @@ -108,6 +310,3 @@ OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() { } -BallController.prototype.onUnload = function() { - this.clearGhostEdges(); -} diff --git a/examples/toys/magBalls/constants.js b/examples/toys/magBalls/constants.js index b692f0908c..d9dee94329 100644 --- a/examples/toys/magBalls/constants.js +++ b/examples/toys/magBalls/constants.js @@ -1,8 +1,10 @@ +MAG_BALLS_DATA_NAME = "magBalls"; + // FIXME make this editable through some script UI, so the user can customize the size of the structure built -SCALE = 0.5; -BALL_SIZE = 0.08 * SCALE; -STICK_LENGTH = 0.24 * SCALE; +MAG_BALLS_SCALE = 0.5; +BALL_SIZE = 0.08 * MAG_BALLS_SCALE; +STICK_LENGTH = 0.24 * MAG_BALLS_SCALE; DEBUG_MAGSTICKS = true; @@ -11,8 +13,6 @@ EDGE_NAME = "MagStick"; BALL_RADIUS = BALL_SIZE / 2.0; -BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; - BALL_DIMENSIONS = { x: BALL_SIZE, y: BALL_SIZE, @@ -45,10 +45,13 @@ BALL_PROTOTYPE = { // 2 millimeters BALL_EPSILON = (.002) / BALL_DISTANCE; +// FIXME better handling of the line bounding box would require putting the +// origin in the middle of the line, not at one end +LINE_DIAGONAL = Math.sqrt((STICK_LENGTH * STICK_LENGTH * 2) * 3) * 1.1; LINE_DIMENSIONS = { - x: 5, - y: 5, - z: 5 + x: LINE_DIAGONAL, + y: LINE_DIAGONAL, + z: LINE_DIAGONAL } LINE_PROTOTYPE = { @@ -75,3 +78,9 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE; // ignoreCollisions: true, // collisionsWillMove: false // } + + +BALL_EDIT_MODE_ADD = "add"; +BALL_EDIT_MODE_MOVE = "move"; +BALL_EDIT_MODE_DELETE = "delete"; +BALL_EDIT_MODE_DELETE_SHAPE = "deleteShape"; diff --git a/examples/toys/magBalls/debugUtils.js b/examples/toys/magBalls/debugUtils.js index 8dadd34679..e69de29bb2 100644 --- a/examples/toys/magBalls/debugUtils.js +++ b/examples/toys/magBalls/debugUtils.js @@ -1,95 +0,0 @@ -findMatchingNode = function(position, nodePositions) { - for (var nodeId in nodePositions) { - var nodePos = nodePositions[nodeId]; - var distance = Vec3.distance(position, nodePos); - if (distance < 0.03) { - return nodeId; - } - } -} - -repairConnections = function() { - var ids = Entities.findEntities(MyAvatar.position, 50); - - // Find all the balls and record their positions - var nodePositions = {}; - for (var i in ids) { - var id = ids[i]; - var properties = Entities.getEntityProperties(id); - if (properties.name == BALL_NAME) { - nodePositions[id] = properties.position; - } - } - - // Now check all the edges to see if they're valid (point to balls) - // and ensure that the balls point back to them - var ballsToEdges = {}; - for (var i in ids) { - var id = ids[i]; - var properties = Entities.getEntityProperties(id); - if (properties.name == EDGE_NAME) { - var startPos = properties.position; - var endPos = Vec3.sum(startPos, properties.linePoints[1]); - var magBallData = getMagBallsData(id); - var update = false; - if (!magBallData.start) { - var startNode = findMatchingNode(startPos, nodePositions); - if (startNode) { - logDebug("Found start node " + startNode) - magBallData.start = startNode; - update = true; - } - } - if (!magBallData.end) { - var endNode = findMatchingNode(endPos, nodePositions); - if (endNode) { - logDebug("Found end node " + endNode) - magBallData.end = endNode; - update = true; - } - } - if (!magBallData.start || !magBallData.end) { - logDebug("Didn't find both ends"); - Entities.deleteEntity(id); - continue; - } - if (!ballsToEdges[magBallData.start]) { - ballsToEdges[magBallData.start] = [ id ]; - } else { - ballsToEdges[magBallData.start].push(id); - } - if (!ballsToEdges[magBallData.end]) { - ballsToEdges[magBallData.end] = [ id ]; - } else { - ballsToEdges[magBallData.end].push(id); - } - if (update) { - logDebug("Updating incomplete edge " + id); - magBallData.length = BALL_DISTANCE; - setMagBallsData(id, magBallData); - } - } - } - for (var nodeId in ballsToEdges) { - var magBallData = getMagBallsData(nodeId); - var edges = magBallData.edges || []; - var edgeHash = {}; - for (var i in edges) { - edgeHash[edges[i]] = true; - } - var update = false; - for (var i in ballsToEdges[nodeId]) { - var edgeId = ballsToEdges[nodeId][i]; - if (!edgeHash[edgeId]) { - update = true; - edgeHash[edgeId] = true; - edges.push(edgeId); - } - } - if (update) { - logDebug("Fixing node with missing edge data"); - magBallData.edges = edges; - setMagBallsData(nodeId, magBallData); - } - } -} diff --git a/examples/toys/magBalls/edgeSpring.js b/examples/toys/magBalls/edgeSpring.js index 852c9257c2..e1b717c39b 100644 --- a/examples/toys/magBalls/edgeSpring.js +++ b/examples/toys/magBalls/edgeSpring.js @@ -9,7 +9,10 @@ EdgeSpring = function(edgeId, graph) { this.desiredLength = magBallsData.length || BALL_DISTANCE; } -EdgeSpring.prototype.adjust = function(results) { +// FIXME as iterations increase, start introducing some randomness +// to the adjustment so that we avoid false equilibriums +// Alternatively, larger iterations could increase the acceptable variance +EdgeSpring.prototype.adjust = function(results, iterations) { var startPos = this.getAdjustedPosition(this.start, results); var endPos = this.getAdjustedPosition(this.end, results); var vector = Vec3.subtract(endPos, startPos); diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js index 307be9f5e1..b689fb1272 100644 --- a/examples/toys/magBalls/magBalls.js +++ b/examples/toys/magBalls/magBalls.js @@ -25,10 +25,6 @@ MagBalls = function() { this.refresh(); var _this = this; - //Script.update.connect(function(deltaTime) { - // _this.onUpdate(deltaTime); - //}); - Script.scriptEnding.connect(function() { _this.onCleanup(); }); @@ -61,8 +57,13 @@ MagBalls.prototype.onUpdate = function(deltaTime) { if (!this.unstableEdges[edgeId]) { continue; } - adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults); + // FIXME need to add some randomness to this so that objects don't hit a + // false equilibrium + // FIXME should this be done node-wise, to more easily account for the number of edge + // connections for a node? + adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults, this.adjustIterations); } + for (var nodeId in nodeAdjustResults) { var curPos = this.getNodePosition(nodeId); var newPos = nodeAdjustResults[nodeId]; @@ -71,18 +72,24 @@ MagBalls.prototype.onUpdate = function(deltaTime) { fixupEdges[edgeId] = true; } // logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm"); - Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED }); + Entities.editEntity(nodeId, { + position: newPos, + // DEBUGGING, flashes moved balls + // color: COLORS.RED + }); } + // DEBUGGING, flashes moved balls + //Script.setTimeout(function(){ + // for (var nodeId in nodeAdjustResults) { + // Entities.editEntity(nodeId, { color: BALL_COLOR }); + // } + //}, ((UPDATE_INTERVAL * 1000) / 2)); + for (var edgeId in fixupEdges) { this.fixupEdge(edgeId); } - - Script.setTimeout(function(){ - for (var nodeId in nodeAdjustResults) { - Entities.editEntity(nodeId, { color: BALL_COLOR }); - } - }, ((UPDATE_INTERVAL * 1000) / 2)); + if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) { if (adjusted) { @@ -357,4 +364,96 @@ MagBalls.prototype.onEntityAdded = function(entityId) { if (properties.name == BALL_NAME || properties.name == EDGE_NAME) { this.refreshNeeded = 1; } -} \ No newline at end of file +} + +findMatchingNode = function(position, nodePositions) { + for (var nodeId in nodePositions) { + var nodePos = nodePositions[nodeId]; + var distance = Vec3.distance(position, nodePos); + if (distance < 0.03) { + return nodeId; + } + } +} + + +MagBalls.prototype.repair = function() { + // Find all the balls and record their positions + var nodePositions = {}; + for (var nodeId in this.nodes) { + nodePositions[nodeId] = this.getNodePosition(nodeId); + } + + // Now check all the edges to see if they're valid (point to balls) + // and ensure that the balls point back to them + var ballsToEdges = {}; + + // WARNING O(n^2) algorithm, every edge that is broken does + // an O(N) search against the nodes + for (var edgeId in this.edges) { + var properties = Entities.getEntityProperties(edgeId); + var startPos = properties.position; + var endPos = Vec3.sum(startPos, properties.linePoints[1]); + var magBallData = getMagBallsData(edgeId); + var update = false; + if (!magBallData.start) { + var startNode = findMatchingNode(startPos, nodePositions); + if (startNode) { + logDebug("Found start node " + startNode) + magBallData.start = startNode; + update = true; + } + } + if (!magBallData.end) { + var endNode = findMatchingNode(endPos, nodePositions); + if (endNode) { + logDebug("Found end node " + endNode) + magBallData.end = endNode; + update = true; + } + } + if (!magBallData.start || !magBallData.end) { + logDebug("Didn't find both ends"); + this.destroyEdge(edgeId); + continue; + } + if (!ballsToEdges[magBallData.start]) { + ballsToEdges[magBallData.start] = [ edgeId ]; + } else { + ballsToEdges[magBallData.start].push(edgeId); + } + if (!ballsToEdges[magBallData.end]) { + ballsToEdges[magBallData.end] = [ edgeId ]; + } else { + ballsToEdges[magBallData.end].push(edgeId); + } + if (update) { + logDebug("Updating incomplete edge " + edgeId); + magBallData.length = BALL_DISTANCE; + setMagBallsData(edgeId, magBallData); + } + } + for (var nodeId in ballsToEdges) { + var magBallData = getMagBallsData(nodeId); + var edges = magBallData.edges || []; + var edgeHash = {}; + for (var i in edges) { + edgeHash[edges[i]] = true; + } + var update = false; + for (var i in ballsToEdges[nodeId]) { + var edgeId = ballsToEdges[nodeId][i]; + if (!edgeHash[edgeId]) { + update = true; + edgeHash[edgeId] = true; + edges.push(edgeId); + } + } + if (update) { + logDebug("Fixing node with missing edge data"); + magBallData.edges = edges; + setMagBallsData(nodeId, magBallData); + } + } +} + diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index c173c5927f..f14fcf4dc0 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -147,7 +147,12 @@ QScriptValue Web3DOverlay::getProperty(const QString& property) { void Web3DOverlay::setURL(const QString& url) { _url = url; - _isLoaded = false; + if (_webSurface) { + AbstractViewStateInterface::instance()->postLambdaEvent([this, url] { + _webSurface->getRootItem()->setProperty("url", url); + }); + } + } bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) { diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index 99f404eaec..b22bb36d83 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -23,10 +23,12 @@ out vec3 _normal; out vec3 _color; out vec2 _texCoord0; +out vec4 _position; void main(void) { _color = inColor.rgb; _texCoord0 = inTexCoord0.st; + _position = inPosition; // standard transform TransformCamera cam = getTransformCamera(); From 4a28361e058ed2ef6f827111b179bf7be41710ae Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 3 Sep 2015 22:12:21 -0700 Subject: [PATCH 10/12] Fix mirror to display on Oculus --- .../oculus/OculusDisplayPlugin.cpp | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index 2ed5e69fe7..c8d9993e5a 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -310,8 +310,6 @@ void OculusDisplayPlugin::activate() { // not needed since the structure was zeroed on init, but explicit sceneLayer.ColorTexture[1] = nullptr; - PerformanceTimer::setActive(true); - if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd, ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { qFatal("Could not attach to sensor device"); @@ -322,15 +320,12 @@ void OculusDisplayPlugin::activate() { 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()); - _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo->Init(getRecommendedRenderSize()); #endif enableVsync(false); - isVsyncEnabled(); + // Only enable mirroring if we know vsync is disabled + _enableMirror = !isVsyncEnabled(); } void OculusDisplayPlugin::deactivate() { @@ -338,7 +333,6 @@ void OculusDisplayPlugin::deactivate() { makeCurrent(); _sceneFbo.reset(); doneCurrent(); - PerformanceTimer::setActive(false); WindowOpenGLDisplayPlugin::deactivate(); @@ -355,6 +349,16 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi // controlling vsync wglSwapIntervalEXT(0); + // screen mirroring + if (_enableMirror) { + auto windowSize = toGlm(_window->size()); + Context::Viewport(windowSize.x, windowSize.y); + glBindTexture(GL_TEXTURE_2D, finalTexture); + GLenum err = glGetError(); + Q_ASSERT(0 == err); + drawUnitQuad(); + } + _sceneFbo->Bound([&] { auto size = _sceneFbo->size; Context::Viewport(size.x, size.y); @@ -369,25 +373,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi }); auto windowSize = toGlm(_window->size()); - - /* - Two alternatives for mirroring to the screen, the first is to copy our own composited - scene to the window framebuffer, before distortion. Note this only works if we're doing - ui compositing ourselves, and not relying on the Oculus SDK compositor (or we don't want - the UI visible in the output window (unlikely). This should be done before - _sceneFbo->Increment or we're be using the wrong texture - */ - if (_enableMirror) { - _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]; @@ -401,20 +387,6 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi } _sceneFbo->Increment(); - /* - The other alternative for mirroring is to use the Oculus mirror texture support, which - will contain the post-distorted and fully composited scene regardless of how many layers - we send. - Currently generates an error. - */ - //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 } From 6b2897ca8fe233b8c725f24105d9c2eba5e34801 Mon Sep 17 00:00:00 2001 From: Raffi Bedikian Date: Thu, 3 Sep 2015 23:11:53 -0700 Subject: [PATCH 11/12] Fix AA bugs with display plugins and mini-mirror --- libraries/render-utils/src/AntialiasingEffect.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 283561fc57..0e362bfb49 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -61,6 +61,13 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { // Good to go add the brand new pipeline _antialiasingPipeline.reset(gpu::Pipeline::create(program, state)); } + + int w = DependencyManager::get()->getFrameBufferSize().width(); + int h = DependencyManager::get()->getFrameBufferSize().height(); + if (w != _antialiasingBuffer->getWidth() || h != _antialiasingBuffer->getHeight()) { + _antialiasingBuffer->resize(w, h); + } + return _antialiasingPipeline; } @@ -89,7 +96,14 @@ void Antialiasing::run(const render::SceneContextPointer& sceneContext, const re assert(renderContext->args); assert(renderContext->args->_viewFrustum); + if (renderContext->args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + return; + } + gpu::Batch batch; + + batch.enableStereo(false); + RenderArgs* args = renderContext->args; auto framebufferCache = DependencyManager::get(); From 2f2558724f9e91918e5077b8dfa08b057a83fffe Mon Sep 17 00:00:00 2001 From: Leonardo Murillo Date: Fri, 4 Sep 2015 09:28:28 -0600 Subject: [PATCH 12/12] Including Visual Studio 2010 Redistributable in installer --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a4d193324d..a2eb058ae6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -High Fidelity (hifi) is an early-stage technology -lab experimenting with Virtual Worlds and VR. +High Fidelity (hifi) is an early-stage technology lab experimenting with Virtual Worlds and VR. In this repository you'll find the source to many of the components in our alpha-stage virtual world. The project embraces distributed development