From bffd236640f34f2d0831120afcd39d4267f31fbc Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 29 Sep 2014 16:44:40 -0700 Subject: [PATCH 001/104] add glowLevel, and pulse to all overlays --- interface/src/ui/overlays/Cube3DOverlay.cpp | 53 ++++++-- interface/src/ui/overlays/ImageOverlay.cpp | 5 +- interface/src/ui/overlays/Line3DOverlay.cpp | 18 ++- .../src/ui/overlays/LocalModelsOverlay.cpp | 12 ++ .../src/ui/overlays/LocalVoxelsOverlay.cpp | 17 ++- interface/src/ui/overlays/ModelOverlay.cpp | 47 ++----- interface/src/ui/overlays/Overlay.cpp | 124 ++++++++++++++++++ interface/src/ui/overlays/Overlay.h | 42 +++++- interface/src/ui/overlays/Sphere3DOverlay.cpp | 47 +++++-- interface/src/ui/overlays/TextOverlay.cpp | 26 +++- interface/src/ui/overlays/TextOverlay.h | 1 + interface/src/ui/overlays/Volume3DOverlay.cpp | 74 ++++++++++- interface/src/ui/overlays/Volume3DOverlay.h | 16 ++- 13 files changed, 404 insertions(+), 78 deletions(-) diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index e9a366c6dc..379b74a27f 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -13,8 +13,11 @@ #include #include +#include +#include "Application.h" #include "Cube3DOverlay.h" +#include "renderer/GlowEffect.h" Cube3DOverlay::Cube3DOverlay() { } @@ -27,22 +30,44 @@ void Cube3DOverlay::render() { return; // do nothing if we're not visible } - const float MAX_COLOR = 255; - glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha); - - glDisable(GL_LIGHTING); - glPushMatrix(); - glTranslatef(_position.x + _size * 0.5f, - _position.y + _size * 0.5f, - _position.z + _size * 0.5f); - glLineWidth(_lineWidth); - if (_isSolid) { - glutSolidCube(_size); - } else { - glLineWidth(_lineWidth); - glutWireCube(_size); + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); } + + float alpha = getAlpha(); + xColor color = getColor(); + const float MAX_COLOR = 255; + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + //glDisable(GL_LIGHTING); + + // TODO: handle registration point?? + glm::vec3 position = getPosition(); + glm::vec3 center = getCenter(); + glm::vec3 dimensions = getDimensions(); + glm::quat rotation = getRotation(); + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glScalef(dimensions.x, dimensions.y, dimensions.z); + if (_isSolid) { + Application::getInstance()->getDeferredLightingEffect()->renderSolidCube(1.0f); + } else { + glLineWidth(_lineWidth); + Application::getInstance()->getDeferredLightingEffect()->renderWireCube(1.0f); + } + glPopMatrix(); glPopMatrix(); + if (glower) { + delete glower; + } } diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index 7104b3aced..0bc63e423b 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -66,7 +66,9 @@ void ImageOverlay::render() { glBindTexture(GL_TEXTURE_2D, _textureID); } const float MAX_COLOR = 255; - glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha); + xColor color = getColor(); + float alpha = getAlpha(); + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); float imageWidth = _textureImage.width(); float imageHeight = _textureImage.height(); @@ -106,6 +108,7 @@ void ImageOverlay::render() { } glVertex2f(_bounds.left(), _bounds.bottom()); glEnd(); + if (_renderImage) { glDisable(GL_TEXTURE_2D); } diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 6c036a898d..4f09eb55e9 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -12,6 +12,7 @@ #include "InterfaceConfig.h" #include "Line3DOverlay.h" +#include "renderer/GlowEffect.h" Line3DOverlay::Line3DOverlay() { @@ -25,16 +26,29 @@ void Line3DOverlay::render() { return; // do nothing if we're not visible } - const float MAX_COLOR = 255; + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); + } + glDisable(GL_LIGHTING); glLineWidth(_lineWidth); - glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha); + + float alpha = getAlpha(); + xColor color = getColor(); + const float MAX_COLOR = 255; + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); glBegin(GL_LINES); glVertex3f(_position.x, _position.y, _position.z); glVertex3f(_end.x, _end.y, _end.z); glEnd(); glEnable(GL_LIGHTING); + + if (glower) { + delete glower; + } } void Line3DOverlay::setProperties(const QScriptValue& properties) { diff --git a/interface/src/ui/overlays/LocalModelsOverlay.cpp b/interface/src/ui/overlays/LocalModelsOverlay.cpp index e4aaa04b20..efecd74009 100644 --- a/interface/src/ui/overlays/LocalModelsOverlay.cpp +++ b/interface/src/ui/overlays/LocalModelsOverlay.cpp @@ -27,6 +27,13 @@ void LocalModelsOverlay::update(float deltatime) { void LocalModelsOverlay::render() { if (_visible) { + + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); + } + glPushMatrix(); { Application* app = Application::getInstance(); glm::vec3 oldTranslation = app->getViewMatrixTranslation(); @@ -34,5 +41,10 @@ void LocalModelsOverlay::render() { _entityTreeRenderer->render(); Application::getInstance()->setViewMatrixTranslation(oldTranslation); } glPopMatrix(); + + if (glower) { + delete glower; + } + } } diff --git a/interface/src/ui/overlays/LocalVoxelsOverlay.cpp b/interface/src/ui/overlays/LocalVoxelsOverlay.cpp index 93736ddccb..6a474fb33e 100644 --- a/interface/src/ui/overlays/LocalVoxelsOverlay.cpp +++ b/interface/src/ui/overlays/LocalVoxelsOverlay.cpp @@ -52,12 +52,25 @@ void LocalVoxelsOverlay::update(float deltatime) { } void LocalVoxelsOverlay::render() { - if (_visible && _size > 0 && _voxelSystem && _voxelSystem->isInitialized()) { + glm::vec3 dimensions = getDimensions(); + float size = glm::length(dimensions); + if (_visible && size > 0 && _voxelSystem && _voxelSystem->isInitialized()) { + + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); + } + glPushMatrix(); { glTranslatef(_position.x, _position.y, _position.z); - glScalef(_size, _size, _size); + glScalef(dimensions.x, dimensions.y, dimensions.z); _voxelSystem->render(); } glPopMatrix(); + + if (glower) { + delete glower; + } } } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 8d89fe28f0..793ebbc3e3 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -43,45 +43,16 @@ void ModelOverlay::render() { } if (_model.isActive()) { - if (_model.isRenderable()) { - _model.render(_alpha); - } - bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds); - if (displayModelBounds) { - glm::vec3 unRotatedMinimum = _model.getUnscaledMeshExtents().minimum; - glm::vec3 unRotatedMaximum = _model.getUnscaledMeshExtents().maximum; - glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum; - - float width = unRotatedExtents.x; - float height = unRotatedExtents.y; - float depth = unRotatedExtents.z; - - Extents rotatedExtents = _model.getUnscaledMeshExtents(); - rotatedExtents.rotate(_rotation); - - glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum; - - const glm::vec3& modelScale = _model.getScale(); - - glPushMatrix(); { - glTranslatef(_position.x, _position.y, _position.z); - - // draw the rotated bounding cube - glColor4f(0.0f, 0.0f, 1.0f, 1.0f); - glPushMatrix(); { - glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z); - glutWireCube(1.0); - } glPopMatrix(); - - // draw the model relative bounding box - glm::vec3 axis = glm::axis(_rotation); - glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z); - glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z); - glColor3f(0.0f, 1.0f, 0.0f); - glutWireCube(1.0); - - } glPopMatrix(); + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); + } + _model.render(getAlpha()); + if (glower) { + delete glower; + } } } } diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 9d492c6e50..e4987e9240 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -23,6 +23,16 @@ Overlay::Overlay() : _parent(NULL), _isLoaded(true), _alpha(DEFAULT_ALPHA), + _glowLevel(0.0f), + _pulse(0.0f), + _pulseMax(0.0f), + _pulseMin(0.0f), + _pulsePeriod(1.0f), + _pulseDirection(1.0f), + _lastPulseUpdate(usecTimestampNow()), + _glowLevelPulse(0.0f), + _alphaPulse(0.0f), + _colorPulse(0.0f), _color(DEFAULT_OVERLAY_COLOR), _visible(true), _anchor(NO_ANCHOR) @@ -54,6 +64,34 @@ void Overlay::setProperties(const QScriptValue& properties) { setAlpha(properties.property("alpha").toVariant().toFloat()); } + if (properties.property("glowLevel").isValid()) { + setGlowLevel(properties.property("glowLevel").toVariant().toFloat()); + } + + if (properties.property("pulseMax").isValid()) { + setPulseMax(properties.property("pulseMax").toVariant().toFloat()); + } + + if (properties.property("pulseMin").isValid()) { + setPulseMin(properties.property("pulseMin").toVariant().toFloat()); + } + + if (properties.property("pulsePeriod").isValid()) { + setPulsePeriod(properties.property("pulsePeriod").toVariant().toFloat()); + } + + if (properties.property("glowLevelPulse").isValid()) { + setGlowLevelPulse(properties.property("glowLevelPulse").toVariant().toFloat()); + } + + if (properties.property("alphaPulse").isValid()) { + setAlphaPulse(properties.property("alphaPulse").toVariant().toFloat()); + } + + if (properties.property("colorPulse").isValid()) { + setColorPulse(properties.property("colorPulse").toVariant().toFloat()); + } + if (properties.property("visible").isValid()) { setVisible(properties.property("visible").toVariant().toBool()); } @@ -65,3 +103,89 @@ void Overlay::setProperties(const QScriptValue& properties) { } } } + +xColor Overlay::getColor() { + if (_colorPulse == 0.0f) { + return _color; + } + + float pulseLevel = updatePulse(); + xColor result = _color; + if (_colorPulse < 0.0f) { + result.red *= (1.0f - pulseLevel); + result.green *= (1.0f - pulseLevel); + result.blue *= (1.0f - pulseLevel); + } else { + result.red *= pulseLevel; + result.green *= pulseLevel; + result.blue *= pulseLevel; + } + return result; +} + +float Overlay::getAlpha() { + if (_alphaPulse == 0.0f) { + return _alpha; + } + float pulseLevel = updatePulse(); + return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel); +} + +float Overlay::getGlowLevel() { + if (_glowLevelPulse == 0.0f) { + return _glowLevel; + } + float pulseLevel = updatePulse(); + return (_glowLevelPulse >= 0.0f) ? _glowLevel * pulseLevel : _glowLevel * (1.0f - pulseLevel); +} + + +// glow level travels from min to max, then max to min in one period. +float Overlay::updatePulse() { + if (_pulsePeriod <= 0.0f) { + return _pulse; + } + quint64 now = usecTimestampNow(); + quint64 elapsedUSecs = (now - _lastPulseUpdate); + float elapsedSeconds = (float)elapsedUSecs / (float)USECS_PER_SECOND; + float elapsedPeriods = elapsedSeconds / _pulsePeriod; + + // we can safely remove any "full" periods, since those just rotate us back + // to our final glow level + while (elapsedPeriods > 1.0f) { + elapsedPeriods -= 1.0f; + } + _lastPulseUpdate = now; + + + float glowDistance = (_pulseMax - _pulseMin); + float glowDistancePerPeriod = glowDistance * 2.0f; + + // if we're currently traveling from min to max + if (_pulseDirection > 0.0f) { + float glowDelta = glowDistancePerPeriod * elapsedPeriods; + + // if by adding the glowDelta, we would pass our max, then calculate + // the distance from the max back to where we'd land... + if (_pulse + glowDelta >= _pulseMax) { + float glowDeltaToMax = (_pulse + glowDelta) - _pulseMax; + float glowDeltaFromMaxBack = glowDelta - glowDeltaToMax; + glowDelta = -glowDeltaFromMaxBack; + _pulseDirection = -1.0f; + } + _pulse += glowDelta; + } else { + float glowDelta = _pulseDirection * glowDistancePerPeriod * elapsedPeriods; + + // if by subtracting the glowDelta, we would pass our min, then calculate + // the distance from the min back to where we'd land... + if (_pulse + glowDelta <= _pulseMin) { + float glowDeltaToMin = (_pulse + glowDelta) - _pulseMin; + float glowDeltaFromMinBack = glowDelta - glowDeltaToMin; + glowDelta = -glowDeltaFromMinBack; + _pulseDirection = 1.0f; + } + _pulse += glowDelta; + } + return _pulse; +} diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index c5329688ff..623cd4081c 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -42,22 +42,60 @@ public: // getters bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } - const xColor& getColor() const { return _color; } - float getAlpha() const { return _alpha; } + xColor getColor(); + float getAlpha(); + float getGlowLevel(); Anchor getAnchor() const { return _anchor; } + + float getPulseMax() const { return _pulseMax; } + float getPulseMin() const { return _pulseMin; } + float getPulsePeriod() const { return _pulsePeriod; } + float getPulseDirection() const { return _pulseDirection; } + + float getGlowLevelPulse() const { return _glowLevelPulse; } + float getColorPulse() const { return _colorPulse; } + float getAlphaPulse() const { return _alphaPulse; } + // setters void setVisible(bool visible) { _visible = visible; } void setColor(const xColor& color) { _color = color; } void setAlpha(float alpha) { _alpha = alpha; } + void setGlowLevel(float value) { _glowLevel = value; } void setAnchor(Anchor anchor) { _anchor = anchor; } + void setPulseMax(float value) { _pulseMax = value; } + void setPulseMin(float value) { _pulseMin = value; } + void setPulsePeriod(float value) { _pulsePeriod = value; } + void setPulseDirection(float value) { _pulseDirection = value; } + + + void setGlowLevelPulse(float value) { _glowLevelPulse = value; } + void setColorPulse(float value) { _colorPulse = value; } + void setAlphaPulse(float value) { _alphaPulse = value; } + + virtual void setProperties(const QScriptValue& properties); protected: + float updatePulse(); + QGLWidget* _parent; bool _isLoaded; float _alpha; + float _glowLevel; + + float _pulse; + float _pulseMax; + float _pulseMin; + float _pulsePeriod; + float _pulseDirection; + quint64 _lastPulseUpdate; + + float _glowLevelPulse; // ratio of the pulse to the glow level + float _alphaPulse; // ratio of the pulse to the alpha + float _colorPulse; // ratio of the pulse to the color + xColor _color; bool _visible; // should the overlay be drawn at all Anchor _anchor; diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index c2ac92f45c..3302a74f24 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -15,6 +15,7 @@ #include #include "Sphere3DOverlay.h" +#include "renderer/GlowEffect.h" Sphere3DOverlay::Sphere3DOverlay() { } @@ -27,22 +28,46 @@ void Sphere3DOverlay::render() { return; // do nothing if we're not visible } + const int slices = 15; + float alpha = getAlpha(); + xColor color = getColor(); const float MAX_COLOR = 255; - glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha); + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); glDisable(GL_LIGHTING); - glPushMatrix(); - glTranslatef(_position.x, - _position.y, - _position.z); - glLineWidth(_lineWidth); - const int slices = 15; - if (_isSolid) { - glutSolidSphere(_size, slices, slices); - } else { - glutWireSphere(_size, slices, slices); + + glm::vec3 position = getPosition(); + glm::vec3 center = getCenter(); + glm::vec3 dimensions = getDimensions(); + //glm::vec3 halfDimensions = dimensions / 2.0f; + glm::quat rotation = getRotation(); + + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); } + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glScalef(dimensions.x, dimensions.y, dimensions.z); + //Application::getInstance()->getDeferredLightingEffect()->renderSolidCube(1.0f); + if (_isSolid) { + glutSolidSphere(1.0f, slices, slices); + } else { + glutWireSphere(1.0f, slices, slices); + } + glPopMatrix(); glPopMatrix(); + + if (glower) { + delete glower; + } } diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index bc7cbb2cfa..c9718e284c 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -28,13 +28,36 @@ TextOverlay::TextOverlay() : TextOverlay::~TextOverlay() { } +xColor TextOverlay::getBackgroundColor() { + if (_colorPulse == 0.0f) { + return _backgroundColor; + } + + float pulseLevel = updatePulse(); + xColor result = _backgroundColor; + if (_colorPulse < 0.0f) { + result.red *= (1.0f - pulseLevel); + result.green *= (1.0f - pulseLevel); + result.blue *= (1.0f - pulseLevel); + } else { + result.red *= pulseLevel; + result.green *= pulseLevel; + result.blue *= pulseLevel; + } + return result; +} + + void TextOverlay::render() { if (!_visible) { return; // do nothing if we're not visible } + const float MAX_COLOR = 255; - glColor4f(_backgroundColor.red / MAX_COLOR, _backgroundColor.green / MAX_COLOR, _backgroundColor.blue / MAX_COLOR, _alpha); + xColor backgroundColor = getBackgroundColor(); + float alpha = getAlpha(); + glColor4f(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR, alpha); glBegin(GL_QUADS); glVertex2f(_bounds.left(), _bounds.top()); @@ -64,7 +87,6 @@ void TextOverlay::render() { const int lineGap = 2; lineOffset += lineGap; } - } void TextOverlay::setProperties(const QScriptValue& properties) { diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index 78a51179a0..0ef7d6b772 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -43,6 +43,7 @@ public: const QString& getText() const { return _text; } int getLeftMargin() const { return _leftMargin; } int getTopMargin() const { return _topMargin; } + xColor getBackgroundColor(); // setters void setText(const QString& text) { _text = text; } diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index ee780d8329..de1974e3e8 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "Volume3DOverlay.h" @@ -20,7 +21,7 @@ const float DEFAULT_SIZE = 1.0f; const bool DEFAULT_IS_SOLID = false; Volume3DOverlay::Volume3DOverlay() : - _size(DEFAULT_SIZE), + _dimensions(glm::vec3(DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE)), _isSolid(DEFAULT_IS_SOLID) { } @@ -31,8 +32,75 @@ Volume3DOverlay::~Volume3DOverlay() { void Volume3DOverlay::setProperties(const QScriptValue& properties) { Base3DOverlay::setProperties(properties); - if (properties.property("size").isValid()) { - setSize(properties.property("size").toVariant().toFloat()); + QScriptValue dimensions = properties.property("dimensions"); + + // if "dimensions" property was not there, check to see if they included aliases: scale + if (!dimensions.isValid()) { + dimensions = properties.property("scale"); + if (!dimensions.isValid()) { + dimensions = properties.property("size"); + } + } + + if (dimensions.isValid()) { + bool validDimensions = false; + glm::vec3 newDimensions; + + QScriptValue x = dimensions.property("x"); + QScriptValue y = dimensions.property("y"); + QScriptValue z = dimensions.property("z"); + + + if (x.isValid() && y.isValid() && z.isValid()) { + newDimensions.x = x.toVariant().toFloat(); + newDimensions.y = y.toVariant().toFloat(); + newDimensions.z = z.toVariant().toFloat(); + validDimensions = true; + } else { + QScriptValue width = dimensions.property("width"); + QScriptValue height = dimensions.property("height"); + QScriptValue depth = dimensions.property("depth"); + if (width.isValid() && height.isValid() && depth.isValid()) { + newDimensions.x = width.toVariant().toFloat(); + newDimensions.y = height.toVariant().toFloat(); + newDimensions.z = depth.toVariant().toFloat(); + validDimensions = true; + } + } + + // size, scale, dimensions is special, it might just be a single scalar, check that here + if (!validDimensions && dimensions.isNumber()) { + float size = dimensions.toVariant().toFloat(); + newDimensions.x = size; + newDimensions.y = size; + newDimensions.z = size; + validDimensions = true; + } + + if (validDimensions) { + setDimensions(newDimensions); + } + } + + QScriptValue rotation = properties.property("rotation"); + + if (rotation.isValid()) { + glm::quat newRotation; + + // size, scale, dimensions is special, it might just be a single scalar, or it might be a vector, check that here + QScriptValue x = rotation.property("x"); + QScriptValue y = rotation.property("y"); + QScriptValue z = rotation.property("z"); + QScriptValue w = rotation.property("w"); + + + if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { + newRotation.x = x.toVariant().toFloat(); + newRotation.y = y.toVariant().toFloat(); + newRotation.z = z.toVariant().toFloat(); + newRotation.w = w.toVariant().toFloat(); + setRotation(newRotation); + } } if (properties.property("isSolid").isValid()) { diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index b06aea27fd..d83ad05e96 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -14,6 +14,10 @@ // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" +#include +#include +#include + #include #include @@ -27,17 +31,23 @@ public: ~Volume3DOverlay(); // getters - float getSize() const { return _size; } bool getIsSolid() const { return _isSolid; } + const glm::vec3& getPosition() const { return _position; } + const glm::vec3& getCenter() const { return _position; } // TODO: registration point!! + const glm::vec3& getDimensions() const { return _dimensions; } + const glm::quat& getRotation() const { return _rotation; } // setters - void setSize(float size) { _size = size; } + void setSize(float size) { _dimensions = glm::vec3(size, size, size); } void setIsSolid(bool isSolid) { _isSolid = isSolid; } + void setDimensions(const glm::vec3& value) { _dimensions = value; } + void setRotation(const glm::quat& value) { _rotation = value; } virtual void setProperties(const QScriptValue& properties); protected: - float _size; + glm::vec3 _dimensions; + glm::quat _rotation; bool _isSolid; }; From 25b072cb222bd6b0373955d7d3c2cafe1f1ea737 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 29 Sep 2014 16:46:17 -0700 Subject: [PATCH 002/104] first cut at new glowing pulsing select ui in editModels.js --- examples/editModels.js | 130 ++++++++++++++++++++++++++++++++++------- 1 file changed, 109 insertions(+), 21 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index c47c497a78..406dac5081 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -35,6 +35,9 @@ var LASER_LENGTH_FACTOR = 500; var MIN_ANGULAR_SIZE = 2; var MAX_ANGULAR_SIZE = 45; +var allowLargeModels = false; +var allowSmallModels = false; +var wantEntityGlow = false; var LEFT = 0; var RIGHT = 1; @@ -1568,6 +1571,58 @@ var ExportMenu = function (opts) { Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); }; +var SelectionDisplay = function (opts) { + var self = this; + var selectionBox = Overlays.addOverlay("3d", { + position: { x: 1, y: 1, z: 1 }, + scale: 1, + visible: false + }); + var selectionBox = Overlays.addOverlay("cube", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 180, green: 180, blue: 180}, + alpha: 1, + solid: false, + visible: false + }); + + this.cleanup = function () { + Overlays.deleteOverlay(selectionBox); + }; + + this.showSelection = function (properties) { + + Overlays.editOverlay(selectionBox, + { + visible: true, + solid:false, + lineWidth: 3.0, + position: { x: properties.position.x, + y: properties.position.y, + z: properties.position.z }, + + dimensions: properties.dimensions, + rotation: properties.rotation, + + pulseMin: 0.1, + pulseMax: 1.0, + pulsePeriod: 4.0, + glowLevelPulse: 1.0, + alphaPulse: 0.5, + colorPulse: -0.5 + + }); + }; + + this.hideSelection = function () { + Overlays.editOverlay(selectionBox, { visible: false }); + }; + +}; + +var selectionDisplay = new SelectionDisplay(); + var ModelImporter = function (opts) { var self = this; @@ -1974,12 +2029,11 @@ function controller(wichSide) { var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - if (0 < x && angularSize > MIN_ANGULAR_SIZE) { - if (angularSize > MAX_ANGULAR_SIZE) { - print("Angular size too big: " + angularSize); - return { valid: false }; - } + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + if (0 < x && sizeOK) { return { valid: true, x: x, y: y, z: z }; } return { valid: false }; @@ -2017,6 +2071,7 @@ function controller(wichSide) { if (this.glowedIntersectingModel.isKnownID) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); this.glowedIntersectingModel.isKnownID = false; + selectionDisplay.hideSelection(); } if (!this.grabbing) { var intersection = Entities.findRayIntersection({ @@ -2027,9 +2082,15 @@ function controller(wichSide) { var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0; var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14; - if (intersection.accurate && intersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) { + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + if (intersection.accurate && intersection.entityID.isKnownID && sizeOK) { this.glowedIntersectingModel = intersection.entityID; - Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); + + if (wantEntityGlow) { + Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); + } + selectionDisplay.showSelection(intersection.properties); } } } @@ -2100,6 +2161,7 @@ function controller(wichSide) { }); this.oldModelRotation = newRotation; this.oldModelPosition = newPosition; + selectionDisplay.showSelection(Entities.getEntityProperties(this.entityID)); var indicesToRemove = []; for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { @@ -2328,6 +2390,7 @@ function moveEntities() { }); + selectionDisplay.showSelection(Entities.getEntityProperties(leftController.entityID)); leftController.oldModelPosition = newPosition; leftController.oldModelRotation = rotation; leftController.oldModelHalfDiagonal *= ratio; @@ -2526,16 +2589,16 @@ function mousePressEvent(event) { var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - if (0 < x && angularSize > MIN_ANGULAR_SIZE) { - if (angularSize < MAX_ANGULAR_SIZE) { - entitySelected = true; - selectedEntityID = foundEntity; - selectedEntityProperties = properties; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - } else { - print("Angular size too big: " + angularSize); - } + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + entitySelected = true; + selectedEntityID = foundEntity; + selectedEntityProperties = properties; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); } } } @@ -2557,6 +2620,7 @@ function mousePressEvent(event) { print("Clicked on " + selectedEntityID.id + " " + entitySelected); tooltip.updateText(selectedEntityProperties); tooltip.show(true); + selectionDisplay.showSelection(selectedEntityProperties); } } @@ -2577,17 +2641,25 @@ function mouseMoveEvent(event) { Entities.editEntity(glowedEntityID, { glowLevel: 0.0 }); glowedEntityID.id = -1; glowedEntityID.isKnownID = false; + selectionDisplay.hideSelection(); } var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), entityIntersection.properties.position)) * 180 / 3.14; - - if (entityIntersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) { - Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (entityIntersection.entityID.isKnownID && sizeOK) { + if (wantEntityGlow) { + Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); + } glowedEntityID = entityIntersection.entityID; + selectionDisplay.showSelection(entityIntersection.properties); } + } return; } @@ -2708,6 +2780,7 @@ function mouseMoveEvent(event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); + selectionDisplay.showSelection(selectedEntityProperties); } @@ -2745,10 +2818,16 @@ function setupModelMenus() { } Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L", + afterItem: "Paste Models", isCheckable: true }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S", + afterItem: "Allow Select Large Models", isCheckable: true }); Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" }); + + } function cleanupModelMenus() { @@ -2760,6 +2839,8 @@ function cleanupModelMenus() { } Menu.removeMenuItem("Edit", "Paste Models"); + Menu.removeMenuItem("Edit", "Allow Select Large Models"); + Menu.removeMenuItem("Edit", "Allow Select Small Models"); Menu.removeSeparator("File", "Models"); Menu.removeMenuItem("File", "Export Models"); @@ -2774,6 +2855,7 @@ function scriptEnding() { cleanupModelMenus(); tooltip.cleanup(); modelImporter.cleanup(); + selectionDisplay.cleanup(); if (exportMenu) { exportMenu.close(); } @@ -2798,7 +2880,11 @@ var rescalePercentage; function handeMenuEvent(menuItem) { print("menuItemEvent() in JS... menuItem=" + menuItem); - if (menuItem == "Delete") { + if (menuItem == "Allow Select Small Models") { + allowSmallModels = Menu.isOptionChecked("Allow Select Small Models"); + } else if (menuItem == "Allow Select Large Models") { + allowLargeModels = Menu.isOptionChecked("Allow Select Large Models"); + } else if (menuItem == "Delete") { if (leftController.grabbing) { print(" Delete Entity.... leftController.entityID="+ leftController.entityID); Entities.deleteEntity(leftController.entityID); @@ -2999,6 +3085,7 @@ Controller.keyPressEvent.connect(function (event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); somethingChanged = true; + selectionDisplay.showSelection(selectedEntityProperties); } }); @@ -3116,6 +3203,7 @@ Window.nonBlockingFormClosed.connect(function() { properties.color.blue = array[index++].value; } Entities.editEntity(editModelID, properties); + selectionDisplay.showSelection(propeties); } modelSelected = false; }); From 16cdc2614f5bca8b1498d44e40202adc1303ff91 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 30 Sep 2014 10:06:39 -0700 Subject: [PATCH 003/104] added circle overlays --- examples/editModels.js | 263 ++++++++++++++++-- interface/src/ui/overlays/Circle3DOverlay.cpp | 152 ++++++++++ interface/src/ui/overlays/Circle3DOverlay.h | 43 +++ interface/src/ui/overlays/Overlays.cpp | 7 + interface/src/ui/overlays/Sphere3DOverlay.cpp | 1 - 5 files changed, 442 insertions(+), 24 deletions(-) create mode 100644 interface/src/ui/overlays/Circle3DOverlay.cpp create mode 100644 interface/src/ui/overlays/Circle3DOverlay.h diff --git a/examples/editModels.js b/examples/editModels.js index 406dac5081..c669cf7622 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1573,11 +1573,6 @@ var ExportMenu = function (opts) { var SelectionDisplay = function (opts) { var self = this; - var selectionBox = Overlays.addOverlay("3d", { - position: { x: 1, y: 1, z: 1 }, - scale: 1, - visible: false - }); var selectionBox = Overlays.addOverlay("cube", { position: { x:0, y: 0, z: 0}, size: 1, @@ -1587,36 +1582,258 @@ var SelectionDisplay = function (opts) { visible: false }); + var yawOverlayAngles = { x: 90, y: 0, z: 0 }; + var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); + var yawOverlayInner = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 0, green: 195, blue: 255}, + alpha: 0.1, + solid: false, + visible: false, + rotation: yawOverlayRotation + }); + + var yawOverlayOuter = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 0, green: 195, blue: 215}, + alpha: 0.1, + solid: false, + visible: false, + rotation: yawOverlayRotation + }); + + var yawOverlayCurrent = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 255, green: 190, blue: 190}, + alpha: 1, + solid: false, + visible: false, + rotation: yawOverlayRotation + }); + + var yawHandleAngles = { x: 90, y: 90, z: 0 }; + var yawHandleRotation = Quat.fromVec3Degrees(yawHandleAngles); + var yawHandle = Overlays.addOverlay("billboard", { + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + position: { x:0, y: 0, z: 0}, + solid: true, + color: { red: 0, green: 0, blue: 255 }, + alpha: 0.3, + visible: false, + size: 0.1, + scale: 0.1, + rotation: yawHandleRotation, + isFacingAvatar: false + }); + + var pitchOverlayAngles = { x: 0, y: 90, z: 0 }; + var pitchOverlayRotation = Quat.fromVec3Degrees(pitchOverlayAngles); + var pitchOverlay = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 0, green: 255, blue: 0}, + alpha: 1, + solid: false, + visible: false, + rotation: pitchOverlayRotation + }); + + var pitchHandleAngles = { x: 90, y: 0, z: 90 }; + var pitchHandleRotation = Quat.fromVec3Degrees(pitchHandleAngles); + var pitchHandle = Overlays.addOverlay("billboard", { + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + position: { x:0, y: 0, z: 0}, + solid: true, + color: { red: 0, green: 0, blue: 255 }, + alpha: 0.3, + visible: false, + size: 0.1, + scale: 0.1, + rotation: pitchHandleRotation, + isFacingAvatar: false + }); + + var rollOverlayAngles = { x: 0, y: 180, z: 0 }; + var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles); + var rollOverlay = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 255, green: 0, blue: 0}, + alpha: 1, + solid: false, + visible: false, + rotation: rollOverlayRotation + }); + + var rollHandleAngles = { x: 0, y: 0, z: 180 }; + var rollHandleRotation = Quat.fromVec3Degrees(rollHandleAngles); + var rollHandle = Overlays.addOverlay("billboard", { + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + position: { x:0, y: 0, z: 0}, + solid: true, + color: { red: 0, green: 0, blue: 255 }, + alpha: 0.3, + visible: false, + size: 0.1, + scale: 0.1, + rotation: rollHandleRotation, + isFacingAvatar: false + }); + this.cleanup = function () { Overlays.deleteOverlay(selectionBox); + Overlays.deleteOverlay(yawOverlayInner); + Overlays.deleteOverlay(yawOverlayOuter); + Overlays.deleteOverlay(yawOverlayCurrent); + Overlays.deleteOverlay(pitchOverlay); + Overlays.deleteOverlay(rollOverlay); + Overlays.deleteOverlay(yawHandle); + Overlays.deleteOverlay(pitchHandle); + Overlays.deleteOverlay(rollHandle); }; this.showSelection = function (properties) { + + var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; + var innerRadius = diagonal; + var outerRadius = diagonal * 1.15; - Overlays.editOverlay(selectionBox, - { - visible: true, - solid:false, - lineWidth: 3.0, - position: { x: properties.position.x, - y: properties.position.y, - z: properties.position.z }, + Overlays.editOverlay(selectionBox, + { + visible: true, + solid:false, + lineWidth: 2.0, + position: { x: properties.position.x, + y: properties.position.y, + z: properties.position.z }, - dimensions: properties.dimensions, - rotation: properties.rotation, + dimensions: properties.dimensions, + rotation: properties.rotation, - pulseMin: 0.1, - pulseMax: 1.0, - pulsePeriod: 4.0, - glowLevelPulse: 1.0, - alphaPulse: 0.5, - colorPulse: -0.5 + pulseMin: 0.1, + pulseMax: 1.0, + pulsePeriod: 4.0, + glowLevelPulse: 1.0, + alphaPulse: 0.5, + colorPulse: -0.5 - }); + }); + + Overlays.editOverlay(yawOverlayInner, + { + visible: true, + solid:false, + lineWidth: 5.0, + position: { x: properties.position.x, + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z}, + + size: innerRadius, + innerRadius: 0.9 + }); + + Overlays.editOverlay(yawOverlayOuter, + { + visible: true, + solid:false, + lineWidth: 5.0, + position: { x: properties.position.x, + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z}, + + size: outerRadius, + innerRadius: 0.9, + startAt: 90, + endAt: 405, + }); + + Overlays.editOverlay(yawOverlayCurrent, + { + visible: true, + solid:false, + lineWidth: 5.0, + position: { x: properties.position.x, + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z}, + + size: outerRadius, + startAt: 45, + endAt: 90, + innerRadius: 0.9 + }); + + Overlays.editOverlay(yawHandle, + { + visible: true, + position: { x: properties.position.x - (properties.dimensions.x / 2), + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z - (properties.dimensions.z / 2)}, + + //dimensions: properties.dimensions, + //rotation: properties.rotation + }); + + Overlays.editOverlay(pitchOverlay, + { + visible: false, + solid:false, + lineWidth: 5.0, + position: { x: properties.position.x + (properties.dimensions.x / 2), + y: properties.position.y, + z: properties.position.z }, + + //dimensions: properties.dimensions, + size: diagonal, + //rotation: properties.rotation + alpha: 0.5, + }); + + Overlays.editOverlay(pitchHandle, + { + visible: true, + position: { x: properties.position.x + (properties.dimensions.x / 2), + y: properties.position.y + (properties.dimensions.y / 2), + z: properties.position.z - (properties.dimensions.z / 2)}, + }); + + Overlays.editOverlay(rollOverlay, + { + visible: false, + solid:false, + lineWidth: 5.0, + position: { x: properties.position.x, + y: properties.position.y, + z: properties.position.z + (properties.dimensions.z / 2) }, + + //dimensions: properties.dimensions, + size: diagonal, + //rotation: properties.rotation + alpha: 0.5, + }); + + Overlays.editOverlay(rollHandle, + { + visible: true, + position: { x: properties.position.x - (properties.dimensions.x / 2), + y: properties.position.y + (properties.dimensions.y / 2), + z: properties.position.z + (properties.dimensions.z / 2)}, + }); + }; this.hideSelection = function () { - Overlays.editOverlay(selectionBox, { visible: false }); + Overlays.editOverlay(selectionBox, { visible: false }); + Overlays.editOverlay(yawOverlayInner, { visible: false }); + Overlays.editOverlay(yawOverlayOuter, { visible: false }); + Overlays.editOverlay(yawOverlayCurrent, { visible: false }); + Overlays.editOverlay(pitchOverlay, { visible: false }); + Overlays.editOverlay(rollOverlay, { visible: false }); + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); }; }; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp new file mode 100644 index 0000000000..ac3ffe4ac9 --- /dev/null +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -0,0 +1,152 @@ +// +// Circle3DOverlay.cpp +// interface/src/ui/overlays +// +// 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 this before QGLWidget, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" + +#include +#include + +#include "Circle3DOverlay.h" +#include "renderer/GlowEffect.h" + +Circle3DOverlay::Circle3DOverlay() : + _startAt(0.0f), + _endAt(360.0f), + _outerRadius(1.0f), + _innerRadius(0.0f) +{ +} + +Circle3DOverlay::~Circle3DOverlay() { +} + +void Circle3DOverlay::render() { + if (!_visible) { + return; // do nothing if we're not visible + } + + //const int slices = 15; + float alpha = getAlpha(); + xColor color = getColor(); + const float MAX_COLOR = 255; + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + + glDisable(GL_LIGHTING); + + glm::vec3 position = getPosition(); + glm::vec3 center = getCenter(); + glm::vec3 dimensions = getDimensions(); + glm::quat rotation = getRotation(); + + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); + } + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glScalef(dimensions.x, dimensions.y, dimensions.z); + + // Create the circle in the coordinates origin + float outerRadius = getOuterRadius(); + float innerRadius = getInnerRadius(); + + glLineWidth(_lineWidth); + glBegin(GL_QUAD_STRIP); + + const float PI_OVER_180 = 3.14159265358979f / 180.0f; + const float FULL_CIRCLE = 360.0f; + const float SLICES = 180.0f; // The amount of segment to create the circle + const float SLICE_ANGLE = FULL_CIRCLE / SLICES; + + float startAt = getStartAt(); + float endAt = getEndAt(); + float angle = startAt; + float angleInRadians = angle * PI_OVER_180; + glm::vec2 firstInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); + glm::vec2 firstOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + + glVertex2f(firstInnerPoint.x, firstInnerPoint.y); + glVertex2f(firstOuterPoint.x, firstOuterPoint.y); + + while (angle < endAt) { + angleInRadians = angle * PI_OVER_180; + glm::vec2 thisInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); + glm::vec2 thisOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + + glVertex2f(thisOuterPoint.x, thisOuterPoint.y); + glVertex2f(thisInnerPoint.x, thisInnerPoint.y); + + angle += SLICE_ANGLE; + } + + // get the last slice portion.... + angle = endAt; + angleInRadians = angle * PI_OVER_180; + glm::vec2 lastInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); + glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + + glVertex2f(lastOuterPoint.x, lastOuterPoint.y); + glVertex2f(lastInnerPoint.x, lastInnerPoint.y); + + glEnd(); + + glPopMatrix(); + glPopMatrix(); + + if (glower) { + delete glower; + } +} + +void Circle3DOverlay::setProperties(const QScriptValue &properties) { + Volume3DOverlay::setProperties(properties); + + QScriptValue startAt = properties.property("startAt"); + if (startAt.isValid()) { + setStartAt(startAt.toVariant().toFloat()); + } + + QScriptValue endAt = properties.property("endAt"); + if (endAt.isValid()) { + setEndAt(endAt.toVariant().toFloat()); + } + + QScriptValue outerRadius = properties.property("outerRadius"); + if (outerRadius.isValid()) { + setOuterRadius(outerRadius.toVariant().toFloat()); + } + + QScriptValue innerRadius = properties.property("innerRadius"); + if (innerRadius.isValid()) { + setInnerRadius(innerRadius.toVariant().toFloat()); + } + + qDebug() << "startAt:" << getStartAt(); + qDebug() << "endAt:" << getEndAt(); + qDebug() << "outerRadius:" << getOuterRadius(); + qDebug() << "innerRadius:" << getInnerRadius(); +} + + + + + + + + diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h new file mode 100644 index 0000000000..5e30925a47 --- /dev/null +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -0,0 +1,43 @@ +// +// Circle3DOverlay.h +// interface/src/ui/overlays +// +// 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_Circle3DOverlay_h +#define hifi_Circle3DOverlay_h + +#include "Volume3DOverlay.h" + +class Circle3DOverlay : public Volume3DOverlay { + Q_OBJECT + +public: + Circle3DOverlay(); + ~Circle3DOverlay(); + virtual void render(); + virtual void setProperties(const QScriptValue& properties); + + float getStartAt() const { return _startAt; } + float getEndAt() const { return _endAt; } + float getOuterRadius() const { return _outerRadius; } + float getInnerRadius() const { return _innerRadius; } + + void setStartAt(float value) { _startAt = value; } + void setEndAt(float value) { _endAt = value; } + void setOuterRadius(float value) { _outerRadius = value; } + void setInnerRadius(float value) { _innerRadius = value; } + +protected: + float _startAt; + float _endAt; + float _outerRadius; + float _innerRadius; +}; + + +#endif // hifi_Circle3DOverlay_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 408475ea49..63af4ab282 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -11,6 +11,7 @@ #include #include "BillboardOverlay.h" +#include "Circle3DOverlay.h" #include "Cube3DOverlay.h" #include "ImageOverlay.h" #include "Line3DOverlay.h" @@ -147,6 +148,12 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope thisOverlay->setProperties(properties); created = true; is3D = true; + } else if (type == "circle3d") { + thisOverlay = new Circle3DOverlay(); + thisOverlay->init(_parent); + thisOverlay->setProperties(properties); + created = true; + is3D = true; } else if (type == "line3d") { thisOverlay = new Line3DOverlay(); thisOverlay->init(_parent); diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 3302a74f24..226defcf77 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -40,7 +40,6 @@ void Sphere3DOverlay::render() { glm::vec3 position = getPosition(); glm::vec3 center = getCenter(); glm::vec3 dimensions = getDimensions(); - //glm::vec3 halfDimensions = dimensions / 2.0f; glm::quat rotation = getRotation(); float glowLevel = getGlowLevel(); From 7fc6eecca7046d9ee59c4c4c7821c66cf08c5559 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 30 Sep 2014 11:27:16 -0700 Subject: [PATCH 004/104] added support for dashed lines for 3D Circle overlays --- examples/editModels.js | 17 +-- interface/src/ui/overlays/Circle3DOverlay.cpp | 106 ++++++++++++------ interface/src/ui/overlays/Volume3DOverlay.cpp | 7 ++ interface/src/ui/overlays/Volume3DOverlay.h | 6 +- 4 files changed, 89 insertions(+), 47 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index c669cf7622..fe520235f5 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1589,7 +1589,7 @@ var SelectionDisplay = function (opts) { size: 1, color: { red: 0, green: 195, blue: 255}, alpha: 0.1, - solid: false, + solid: true, visible: false, rotation: yawOverlayRotation }); @@ -1599,7 +1599,7 @@ var SelectionDisplay = function (opts) { size: 1, color: { red: 0, green: 195, blue: 215}, alpha: 0.1, - solid: false, + solid: true, visible: false, rotation: yawOverlayRotation }); @@ -1610,8 +1610,9 @@ var SelectionDisplay = function (opts) { color: { red: 255, green: 190, blue: 190}, alpha: 1, solid: false, + isDashedLine: true, visible: false, - rotation: yawOverlayRotation + rotation: yawOverlayRotation, }); var yawHandleAngles = { x: 90, y: 90, z: 0 }; @@ -1619,7 +1620,6 @@ var SelectionDisplay = function (opts) { var yawHandle = Overlays.addOverlay("billboard", { url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", position: { x:0, y: 0, z: 0}, - solid: true, color: { red: 0, green: 0, blue: 255 }, alpha: 0.3, visible: false, @@ -1636,7 +1636,6 @@ var SelectionDisplay = function (opts) { size: 1, color: { red: 0, green: 255, blue: 0}, alpha: 1, - solid: false, visible: false, rotation: pitchOverlayRotation }); @@ -1646,7 +1645,6 @@ var SelectionDisplay = function (opts) { var pitchHandle = Overlays.addOverlay("billboard", { url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", position: { x:0, y: 0, z: 0}, - solid: true, color: { red: 0, green: 0, blue: 255 }, alpha: 0.3, visible: false, @@ -1663,7 +1661,6 @@ var SelectionDisplay = function (opts) { size: 1, color: { red: 255, green: 0, blue: 0}, alpha: 1, - solid: false, visible: false, rotation: rollOverlayRotation }); @@ -1673,7 +1670,6 @@ var SelectionDisplay = function (opts) { var rollHandle = Overlays.addOverlay("billboard", { url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", position: { x:0, y: 0, z: 0}, - solid: true, color: { red: 0, green: 0, blue: 255 }, alpha: 0.3, visible: false, @@ -1725,7 +1721,6 @@ var SelectionDisplay = function (opts) { Overlays.editOverlay(yawOverlayInner, { visible: true, - solid:false, lineWidth: 5.0, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), @@ -1738,7 +1733,6 @@ var SelectionDisplay = function (opts) { Overlays.editOverlay(yawOverlayOuter, { visible: true, - solid:false, lineWidth: 5.0, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), @@ -1753,7 +1747,6 @@ var SelectionDisplay = function (opts) { Overlays.editOverlay(yawOverlayCurrent, { visible: true, - solid:false, lineWidth: 5.0, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), @@ -1779,7 +1772,6 @@ var SelectionDisplay = function (opts) { Overlays.editOverlay(pitchOverlay, { visible: false, - solid:false, lineWidth: 5.0, position: { x: properties.position.x + (properties.dimensions.x / 2), y: properties.position.y, @@ -1802,7 +1794,6 @@ var SelectionDisplay = function (opts) { Overlays.editOverlay(rollOverlay, { visible: false, - solid:false, lineWidth: 5.0, position: { x: properties.position.x, y: properties.position.y, diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index ac3ffe4ac9..0f1fcb4cb6 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -32,6 +32,11 @@ void Circle3DOverlay::render() { if (!_visible) { return; // do nothing if we're not visible } + + float PI_OVER_180 = 3.14159265358979f / 180.0f; + float FULL_CIRCLE = 360.0f; + float SLICES = 180.0f; // The amount of segment to create the circle + float SLICE_ANGLE = FULL_CIRCLE / SLICES; //const int slices = 15; float alpha = getAlpha(); @@ -63,48 +68,81 @@ void Circle3DOverlay::render() { glScalef(dimensions.x, dimensions.y, dimensions.z); // Create the circle in the coordinates origin - float outerRadius = getOuterRadius(); - float innerRadius = getInnerRadius(); - - glLineWidth(_lineWidth); - glBegin(GL_QUAD_STRIP); - - const float PI_OVER_180 = 3.14159265358979f / 180.0f; - const float FULL_CIRCLE = 360.0f; - const float SLICES = 180.0f; // The amount of segment to create the circle - const float SLICE_ANGLE = FULL_CIRCLE / SLICES; - + float outerRadius = getOuterRadius(); + float innerRadius = getInnerRadius(); // only used in solid case float startAt = getStartAt(); float endAt = getEndAt(); - float angle = startAt; - float angleInRadians = angle * PI_OVER_180; - glm::vec2 firstInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); - glm::vec2 firstOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); - glVertex2f(firstInnerPoint.x, firstInnerPoint.y); - glVertex2f(firstOuterPoint.x, firstOuterPoint.y); + glLineWidth(_lineWidth); + + // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise + // we just draw a line... + if (getIsSolid()) { + glBegin(GL_QUAD_STRIP); - while (angle < endAt) { - angleInRadians = angle * PI_OVER_180; - glm::vec2 thisInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); - glm::vec2 thisOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + float angle = startAt; + float angleInRadians = angle * PI_OVER_180; + glm::vec2 firstInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); + glm::vec2 firstOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); - glVertex2f(thisOuterPoint.x, thisOuterPoint.y); - glVertex2f(thisInnerPoint.x, thisInnerPoint.y); + glVertex2f(firstInnerPoint.x, firstInnerPoint.y); + glVertex2f(firstOuterPoint.x, firstOuterPoint.y); + + while (angle < endAt) { + angleInRadians = angle * PI_OVER_180; + glm::vec2 thisInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); + glm::vec2 thisOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + + glVertex2f(thisOuterPoint.x, thisOuterPoint.y); + glVertex2f(thisInnerPoint.x, thisInnerPoint.y); - angle += SLICE_ANGLE; - } + angle += SLICE_ANGLE; + } - // get the last slice portion.... - angle = endAt; - angleInRadians = angle * PI_OVER_180; - glm::vec2 lastInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); - glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + // get the last slice portion.... + angle = endAt; + angleInRadians = angle * PI_OVER_180; + glm::vec2 lastInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); + glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); - glVertex2f(lastOuterPoint.x, lastOuterPoint.y); - glVertex2f(lastInnerPoint.x, lastInnerPoint.y); + glVertex2f(lastOuterPoint.x, lastOuterPoint.y); + glVertex2f(lastInnerPoint.x, lastInnerPoint.y); - glEnd(); + glEnd(); + } else { + if (getIsDashedLine()) { + glBegin(GL_LINES); + } else { + glBegin(GL_LINE_STRIP); + } + + + float angle = startAt; + float angleInRadians = angle * PI_OVER_180; + glm::vec2 firstPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + glVertex2f(firstPoint.x, firstPoint.y); + + while (angle < endAt) { + angle += SLICE_ANGLE; + angleInRadians = angle * PI_OVER_180; + glm::vec2 thisPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + glVertex2f(thisPoint.x, thisPoint.y); + + if (getIsDashedLine()) { + angle += SLICE_ANGLE / 2.0f; // short gap + angleInRadians = angle * PI_OVER_180; + glm::vec2 dashStartPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + glVertex2f(dashStartPoint.x, dashStartPoint.y); + } + } + + // get the last slice portion.... + angle = endAt; + angleInRadians = angle * PI_OVER_180; + glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); + glVertex2f(lastOuterPoint.x, lastOuterPoint.y); + glEnd(); + } glPopMatrix(); glPopMatrix(); @@ -141,6 +179,8 @@ void Circle3DOverlay::setProperties(const QScriptValue &properties) { qDebug() << "endAt:" << getEndAt(); qDebug() << "outerRadius:" << getOuterRadius(); qDebug() << "innerRadius:" << getInnerRadius(); + qDebug() << "getIsSolid:" << getIsSolid(); + qDebug() << "getIsDashedLine:" << getIsDashedLine(); } diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index de1974e3e8..04956fae25 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -115,4 +115,11 @@ void Volume3DOverlay::setProperties(const QScriptValue& properties) { if (properties.property("wire").isValid()) { setIsSolid(!properties.property("wire").toVariant().toBool()); } + + if (properties.property("isDashedLine").isValid()) { + setIsDashedLine(properties.property("isDashedLine").toVariant().toBool()); + } + if (properties.property("dashed").isValid()) { + setIsDashedLine(!properties.property("dashed").toVariant().toBool()); + } } diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index d83ad05e96..94c261ef2f 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -32,14 +32,17 @@ public: // getters bool getIsSolid() const { return _isSolid; } + bool getIsDashedLine() const { return _isDashedLine; } + bool getIsSolidLine() const { return !_isDashedLine; } const glm::vec3& getPosition() const { return _position; } - const glm::vec3& getCenter() const { return _position; } // TODO: registration point!! + const glm::vec3& getCenter() const { return _position; } // TODO: consider adding registration point!! const glm::vec3& getDimensions() const { return _dimensions; } const glm::quat& getRotation() const { return _rotation; } // setters void setSize(float size) { _dimensions = glm::vec3(size, size, size); } void setIsSolid(bool isSolid) { _isSolid = isSolid; } + void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } void setDimensions(const glm::vec3& value) { _dimensions = value; } void setRotation(const glm::quat& value) { _rotation = value; } @@ -49,6 +52,7 @@ protected: glm::vec3 _dimensions; glm::quat _rotation; bool _isSolid; + bool _isDashedLine; }; From e01113d2a7545c0d0dacb6ebe04e70a4daeb3358 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 30 Sep 2014 12:01:44 -0700 Subject: [PATCH 005/104] Simplifications suggested by Andrew. --- interface/src/MetavoxelSystem.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index ae82e57017..c307a37eaa 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -1899,12 +1899,9 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { int largestI = (largestIndex == 0) ? 1 : 2; float sjj = d[largestJ][largestJ]; float sii = d[largestI][largestI]; - float angle = (sii == sjj ? PI_OVER_TWO : glm::atan(2.0f * d[largestJ][largestI], sjj - sii)) / 2.0f; + float angle = glm::atan(2.0f * d[largestJ][largestI], sjj - sii) / 2.0f; glm::quat rotation = glm::angleAxis(angle, largestIndex == 0 ? glm::vec3(0.0f, 0.0f, -1.0f) : (largestIndex == 1 ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(-1.0f, 0.0f, 0.0f))); - if (rotation.w == 0.0f) { - break; - } combinedRotation = glm::normalize(rotation * combinedRotation); glm::mat3 matrix = glm::mat3_cast(combinedRotation); d = matrix * ata * glm::transpose(matrix); From 001af5f982771a892b31750d8863533b155b28c5 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 30 Sep 2014 12:17:35 -0700 Subject: [PATCH 006/104] Simplify the message situation by removing color-only edits. --- interface/src/ui/MetavoxelEditor.cpp | 10 ++-- .../metavoxels/src/MetavoxelMessages.cpp | 44 --------------- libraries/metavoxels/src/MetavoxelMessages.h | 53 ------------------- libraries/networking/src/PacketHeaders.cpp | 2 +- 4 files changed, 6 insertions(+), 103 deletions(-) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index bb62bee9a1..f2288a1f97 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1284,7 +1284,7 @@ HeightfieldColorBrushTool::HeightfieldColorBrushTool(MetavoxelEditor* editor) : } QVariant HeightfieldColorBrushTool::createEdit(bool alternate) { - return QVariant::fromValue(PaintHeightfieldColorEdit(_position, _radius->value(), + return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), SharedObjectPointer(), alternate ? QColor() : _color->getColor())); } @@ -1334,8 +1334,8 @@ void VoxelColorBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& ma // ensure that color is either 100% transparent or 100% opaque QColor color = _color->getColor(); color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelColorBoxEdit(Box(minimum, maximum), - _editor->getGridSpacing(), color)) }; + MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialBoxEdit(Box(minimum, maximum), + _editor->getGridSpacing(), SharedObjectPointer(), color)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } @@ -1454,8 +1454,8 @@ void VoxelColorSphereTool::applyValue(const glm::vec3& position, float radius) { // ensure that color is either 100% transparent or 100% opaque QColor color = _color->getColor(); color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelColorSphereEdit(position, radius, - _editor->getGridSpacing(), color)) }; + MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSphereEdit(position, radius, + _editor->getGridSpacing(), SharedObjectPointer(), color)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 791604f63f..04ed5c5d02 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -408,12 +408,6 @@ void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObje data.guide(visitor); } -PaintHeightfieldColorEdit::PaintHeightfieldColorEdit(const glm::vec3& position, float radius, const QColor& color) : - position(position), - radius(radius), - color(color) { -} - class PaintHeightfieldMaterialEditVisitor : public MetavoxelVisitor { public: @@ -599,11 +593,6 @@ int PaintHeightfieldMaterialEditVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } -void PaintHeightfieldColorEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - PaintHeightfieldMaterialEditVisitor visitor(position, radius, SharedObjectPointer(), color); - data.guide(visitor); -} - PaintHeightfieldMaterialEdit::PaintHeightfieldMaterialEdit(const glm::vec3& position, float radius, const SharedObjectPointer& material, const QColor& averageColor) : position(position), @@ -617,12 +606,6 @@ void PaintHeightfieldMaterialEdit::apply(MetavoxelData& data, const WeakSharedOb data.guide(visitor); } -VoxelColorBoxEdit::VoxelColorBoxEdit(const Box& region, float granularity, const QColor& color) : - region(region), - granularity(granularity), - color(color) { -} - const int VOXEL_BLOCK_SIZE = 16; const int VOXEL_BLOCK_SAMPLES = VOXEL_BLOCK_SIZE + 1; const int VOXEL_BLOCK_AREA = VOXEL_BLOCK_SAMPLES * VOXEL_BLOCK_SAMPLES; @@ -849,15 +832,6 @@ int VoxelMaterialBoxEditVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } -void VoxelColorBoxEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - // expand to fit the entire edit - while (!data.getBounds().contains(region)) { - data.expand(); - } - VoxelMaterialBoxEditVisitor visitor(region, granularity, SharedObjectPointer(), color); - data.guide(visitor); -} - VoxelMaterialBoxEdit::VoxelMaterialBoxEdit(const Box& region, float granularity, const SharedObjectPointer& material, const QColor& averageColor) : region(region), @@ -875,13 +849,6 @@ void VoxelMaterialBoxEdit::apply(MetavoxelData& data, const WeakSharedObjectHash data.guide(visitor); } -VoxelColorSphereEdit::VoxelColorSphereEdit(const glm::vec3& center, float radius, float granularity, const QColor& color) : - center(center), - radius(radius), - granularity(granularity), - color(color) { -} - class VoxelMaterialSphereEditVisitor : public MetavoxelVisitor { public: @@ -1148,17 +1115,6 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } - -void VoxelColorSphereEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - // expand to fit the entire edit - glm::vec3 extents(radius, radius, radius); - Box bounds(center - extents, center + extents); - while (!data.getBounds().contains(bounds)) { - data.expand(); - } - VoxelMaterialSphereEditVisitor visitor(center, radius, bounds, granularity, SharedObjectPointer(), color); - data.guide(visitor); -} VoxelMaterialSphereEdit::VoxelMaterialSphereEdit(const glm::vec3& center, float radius, float granularity, const SharedObjectPointer& material, const QColor& averageColor) : diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 2eb5684d77..fec8e69aaa 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -224,23 +224,6 @@ public: DECLARE_STREAMABLE_METATYPE(PaintHeightfieldHeightEdit) -/// An edit that sets a region of a heightfield color. -class PaintHeightfieldColorEdit : public MetavoxelEdit { - STREAMABLE - -public: - - STREAM glm::vec3 position; - STREAM float radius; - STREAM QColor color; - - PaintHeightfieldColorEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, const QColor& color = QColor()); - - virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; -}; - -DECLARE_STREAMABLE_METATYPE(PaintHeightfieldColorEdit) - /// An edit that sets a region of a heightfield material. class PaintHeightfieldMaterialEdit : public MetavoxelEdit { STREAMABLE @@ -260,23 +243,6 @@ public: DECLARE_STREAMABLE_METATYPE(PaintHeightfieldMaterialEdit) -/// An edit that sets the color of voxels within a box to a value. -class VoxelColorBoxEdit : public MetavoxelEdit { - STREAMABLE - -public: - - STREAM Box region; - STREAM float granularity; - STREAM QColor color; - - VoxelColorBoxEdit(const Box& region = Box(), float granularity = 0.0f, const QColor& color = QColor()); - - virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; -}; - -DECLARE_STREAMABLE_METATYPE(VoxelColorBoxEdit) - /// An edit that sets the materials of voxels within a box to a value. class VoxelMaterialBoxEdit : public MetavoxelEdit { STREAMABLE @@ -296,25 +262,6 @@ public: DECLARE_STREAMABLE_METATYPE(VoxelMaterialBoxEdit) -/// An edit that sets the color of voxels within a sphere to a value. -class VoxelColorSphereEdit : public MetavoxelEdit { - STREAMABLE - -public: - - STREAM glm::vec3 center; - STREAM float radius; - STREAM float granularity; - STREAM QColor color; - - VoxelColorSphereEdit(const glm::vec3& center = glm::vec3(), float radius = 0.0f, - float granularity = 0.0f, const QColor& color = QColor()); - - virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; -}; - -DECLARE_STREAMABLE_METATYPE(VoxelColorSphereEdit) - /// An edit that sets the materials of voxels within a sphere to a value. class VoxelMaterialSphereEdit : public MetavoxelEdit { STREAMABLE diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index f04e398b98..14bcd7c3ce 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -85,7 +85,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 3; + return 4; case PacketTypeVoxelData: return VERSION_VOXELS_HAS_FILE_BREAKS; default: From 3969c8bfe0588657a944b3a81d66720b8911c15c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Sep 2014 12:23:04 -0700 Subject: [PATCH 007/104] don't allow a username and password for domain auth, require access token --- domain-server/src/DomainServer.cpp | 81 ++++++------ domain-server/src/DomainServer.h | 4 +- libraries/networking/src/AccountManager.cpp | 115 +++++++++++------- libraries/networking/src/AccountManager.h | 4 + .../networking/src/DataServerAccountInfo.h | 1 + libraries/networking/src/OAuthAccessToken.cpp | 2 +- libraries/networking/src/OAuthAccessToken.h | 2 +- 7 files changed, 123 insertions(+), 86 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index eccd52dfc1..1ca549cbad 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -64,7 +64,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : qRegisterMetaType("DomainServerWebSessionData"); qRegisterMetaTypeStreamOperators("DomainServerWebSessionData"); - + if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) { // we either read a certificate and private key or were not passed one // and completed login or did not need to @@ -75,7 +75,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : loadExistingSessionsFromSettings(); // check if we have the flag that enables dynamic IP - setupDynamicIPAddressUpdating(); + setupDynamicSocketUpdating(); } } @@ -151,6 +151,17 @@ bool DomainServer::optionallySetupOAuth() { const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); _oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString()); + + // if we don't have an oauth provider URL then we default to the default node auth url + if (_oauthProviderURL.isEmpty()) { + _oauthProviderURL = DEFAULT_NODE_AUTH_URL; + } + + // setup our account manager with that _oauthProviderURL + AccountManager& accountManager = AccountManager::getInstance(); + accountManager.disableSettingsFilePersistence(); + accountManager.setAuthURL(_oauthProviderURL); + _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); _hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString(); @@ -225,41 +236,37 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } -const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; -const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD"; - -bool DomainServer::hasOAuthProviderAndAuthInformation() { +bool DomainServer::didSetupAccountManagerWithAccessToken() { + AccountManager& accountManager = AccountManager::getInstance(); + + if (accountManager.hasValidAccessToken()) { + // we already gave the account manager a valid access token + return true; + } if (!_oauthProviderURL.isEmpty()) { + // check for an access-token in our settings, can optionally be overidden by env value + const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; + const QString ENV_ACCESS_TOKEN_KEY = "DOMAIN_SERVER_ACCESS_TOKEN"; - static bool hasAttemptedAuthWithOAuthProvider = false; + QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY); - if (!hasAttemptedAuthWithOAuthProvider) { - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(_oauthProviderURL); + if (accessToken.isEmpty()) { + const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (!accountManager.hasValidAccessToken()) { - // we don't have a valid access token so we need to get one - // check if we have a username and password set via env - QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY); - QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY); - - if (!username.isEmpty() && !password.isEmpty()) { - - accountManager.requestAccessToken(username, password); - - // connect to loginFailed signal from AccountManager so we can quit if that is the case - connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed); - } else { - qDebug() << "Missing access-token or username and password combination. domain-server will now quit."; - QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); - return false; - } + if (accessTokenVariant->canConvert(QMetaType::QString)) { + accessToken = accessTokenVariant->toString(); + } else { + qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present." + << "Set an access token via the web interface, in your user or master config" + << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; + return false; } - - hasAttemptedAuthWithOAuthProvider = true; } + // give this access token to the AccountManager + accountManager.setAccessTokenForCurrentAuthURL(accessToken); + return true; } else { @@ -277,7 +284,7 @@ bool DomainServer::optionallySetupAssignmentPayment() { if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) && settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() && - hasOAuthProviderAndAuthInformation()) { + didSetupAccountManagerWithAccessToken()) { qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString()); @@ -299,20 +306,20 @@ bool DomainServer::optionallySetupAssignmentPayment() { return true; } -void DomainServer::setupDynamicIPAddressUpdating() { - const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip"; +void DomainServer::setupDynamicSocketUpdating() { + const QString ENABLE_DYNAMIC_SOCKET_UPDATING_KEY_PATH = "metaverse.update_sockets"; - const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); + const QVariant* updateSocketValue = valueForKeyPath(_settingsManager.getSettingsMap(), + ENABLE_DYNAMIC_SOCKET_UPDATING_KEY_PATH); - if (settingsMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) && - settingsMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() && - hasOAuthProviderAndAuthInformation()) { + if (updateSocketValue && updateSocketValue->canConvert(QMetaType::Bool) && updateSocketValue->toBool() + && didSetupAccountManagerWithAccessToken()) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); const QUuid& domainID = nodeList->getSessionUUID(); if (!domainID.isNull()) { - qDebug() << "domain-server IP address will be updated for domain with ID" + qDebug() << "domain-server socket will be updated for domain with ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 44b8a15901..183ce67639 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -68,9 +68,9 @@ private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - bool hasOAuthProviderAndAuthInformation(); + bool didSetupAccountManagerWithAccessToken(); bool optionallySetupAssignmentPayment(); - void setupDynamicIPAddressUpdating(); + void setupDynamicSocketUpdating(); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 88e4bad7b2..d8de09e2cb 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -52,7 +52,8 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co AccountManager::AccountManager() : _authURL(), _pendingCallbackMap(), - _accountInfo() + _accountInfo(), + _shouldPersistToSettingsFile(false) { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -76,14 +77,18 @@ void AccountManager::logout() { emit balanceChanged(0); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - - QSettings settings; - settings.beginGroup(ACCOUNTS_GROUP); - - QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); - settings.remove(keyURLString); - - qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file"; + + if (_shouldPersistToSettingsFile) { + QSettings settings; + settings.beginGroup(ACCOUNTS_GROUP); + + QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); + settings.remove(keyURLString); + + qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file"; + } else { + qDebug() << "Cleared data server account info in account manager."; + } emit logoutComplete(); // the username has changed to blank @@ -109,28 +114,29 @@ void AccountManager::setAuthURL(const QUrl& authURL) { if (_authURL != authURL) { _authURL = authURL; - qDebug() << "URL for node authentication has been changed to" << qPrintable(_authURL.toString()); - qDebug() << "Re-setting authentication flow."; - - // check if there are existing access tokens to load from settings - QSettings settings; - settings.beginGroup(ACCOUNTS_GROUP); - - foreach(const QString& key, settings.allKeys()) { - // take a key copy to perform the double slash replacement - QString keyCopy(key); - QUrl keyURL(keyCopy.replace("slashslash", "//")); - - if (keyURL == _authURL) { - // pull out the stored access token and store it in memory - _accountInfo = settings.value(key).value(); - qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString()); - - // profile info isn't guaranteed to be saved too - if (_accountInfo.hasProfile()) { - emit profileChanged(); - } else { - requestProfile(); + qDebug() << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); + + if (_shouldPersistToSettingsFile) { + // check if there are existing access tokens to load from settings + QSettings settings; + settings.beginGroup(ACCOUNTS_GROUP); + + foreach(const QString& key, settings.allKeys()) { + // take a key copy to perform the double slash replacement + QString keyCopy(key); + QUrl keyURL(keyCopy.replace("slashslash", "//")); + + if (keyURL == _authURL) { + // pull out the stored access token and store it in memory + _accountInfo = settings.value(key).value(); + qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString()); + + // profile info isn't guaranteed to be saved too + if (_accountInfo.hasProfile()) { + emit profileChanged(); + } else { + requestProfile(); + } } } } @@ -307,8 +313,9 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { } bool AccountManager::hasValidAccessToken() { - + if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) { + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qDebug() << "An access token is required for requests to" << qPrintable(_authURL.toString()); } @@ -330,6 +337,19 @@ bool AccountManager::checkAndSignalForAccessToken() { return hasToken; } +void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) { + // clear our current DataServerAccountInfo + _accountInfo = DataServerAccountInfo(); + + // start the new account info with a new OAuthAccessToken + OAuthAccessToken newOAuthToken; + newOAuthToken.token = accessToken; + + qDebug() << "Setting new account manager access token to" << accessToken; + + _accountInfo.setAccessToken(newOAuthToken); +} + void AccountManager::requestAccessToken(const QString& login, const QString& password) { NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); @@ -380,12 +400,14 @@ void AccountManager::requestAccessTokenFinished() { _accountInfo.setAccessTokenFromJSON(rootObject); emit loginComplete(rootURL); - - // store this access token into the local settings - QSettings localSettings; - localSettings.beginGroup(ACCOUNTS_GROUP); - localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE), - QVariant::fromValue(_accountInfo)); + + if (_shouldPersistToSettingsFile) { + // store this access token into the local settings + QSettings localSettings; + localSettings.beginGroup(ACCOUNTS_GROUP); + localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE), + QVariant::fromValue(_accountInfo)); + } requestProfile(); } @@ -429,13 +451,16 @@ void AccountManager::requestProfileFinished() { // the username has changed to whatever came back emit usernameChanged(_accountInfo.getUsername()); - // store the whole profile into the local settings - QUrl rootURL = profileReply->url(); - rootURL.setPath(""); - QSettings localSettings; - localSettings.beginGroup(ACCOUNTS_GROUP); - localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE), - QVariant::fromValue(_accountInfo)); + if (_shouldPersistToSettingsFile) { + // store the whole profile into the local settings + QUrl rootURL = profileReply->url(); + rootURL.setPath(""); + QSettings localSettings; + localSettings.beginGroup(ACCOUNTS_GROUP); + localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE), + QVariant::fromValue(_accountInfo)); + } + } else { // TODO: error handling qDebug() << "Error in response for profile"; diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index edccab0b75..46c71dd476 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -59,10 +59,13 @@ public: const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); bool hasAuthEndpoint() { return !_authURL.isEmpty(); } + + void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; } bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } bool hasValidAccessToken(); Q_INVOKABLE bool checkAndSignalForAccessToken(); + void setAccessTokenForCurrentAuthURL(const QString& accessToken); void requestAccessToken(const QString& login, const QString& password); void requestProfile(); @@ -107,6 +110,7 @@ private: QMap _pendingCallbackMap; DataServerAccountInfo _accountInfo; + bool _shouldPersistToSettingsFile; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index dd9540718e..91675594ad 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -27,6 +27,7 @@ public: DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo); const OAuthAccessToken& getAccessToken() const { return _accessToken; } + void setAccessToken(const OAuthAccessToken& accessToken) { _accessToken = accessToken; } void setAccessTokenFromJSON(const QJsonObject& jsonObject); const QString& getUsername() const { return _username; } diff --git a/libraries/networking/src/OAuthAccessToken.cpp b/libraries/networking/src/OAuthAccessToken.cpp index 365b07a935..b8ec58099f 100644 --- a/libraries/networking/src/OAuthAccessToken.cpp +++ b/libraries/networking/src/OAuthAccessToken.cpp @@ -16,7 +16,7 @@ OAuthAccessToken::OAuthAccessToken() : token(), refreshToken(), - expiryTimestamp(0), + expiryTimestamp(-1), tokenType() { diff --git a/libraries/networking/src/OAuthAccessToken.h b/libraries/networking/src/OAuthAccessToken.h index 167bb824da..fcd0f22e39 100644 --- a/libraries/networking/src/OAuthAccessToken.h +++ b/libraries/networking/src/OAuthAccessToken.h @@ -26,7 +26,7 @@ public: QByteArray authorizationHeaderValue() const { return QString("Bearer %1").arg(token).toUtf8(); } - bool isExpired() const { return expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); } + bool isExpired() const { return expiryTimestamp != -1 && expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); } QString token; QString refreshToken; From c3b070060132502da5db9d7c66f13f877ddf1399 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 30 Sep 2014 14:52:44 -0700 Subject: [PATCH 008/104] introduce Planar3DOverlay for common features of rectangle and circle, introduced rectanglular 3D plane --- examples/editModels.js | 90 +++++++++++--- interface/src/ui/overlays/Base3DOverlay.cpp | 54 +++++++- interface/src/ui/overlays/Base3DOverlay.h | 13 ++ .../src/ui/overlays/BillboardOverlay.cpp | 24 +--- interface/src/ui/overlays/BillboardOverlay.h | 3 +- interface/src/ui/overlays/Circle3DOverlay.cpp | 6 +- interface/src/ui/overlays/Circle3DOverlay.h | 4 +- interface/src/ui/overlays/Overlays.cpp | 7 ++ interface/src/ui/overlays/Planar3DOverlay.cpp | 76 ++++++++++++ interface/src/ui/overlays/Planar3DOverlay.h | 45 +++++++ .../src/ui/overlays/Rectangle3DOverlay.cpp | 115 ++++++++++++++++++ .../src/ui/overlays/Rectangle3DOverlay.h | 27 ++++ interface/src/ui/overlays/Volume3DOverlay.cpp | 45 +------ interface/src/ui/overlays/Volume3DOverlay.h | 13 -- 14 files changed, 424 insertions(+), 98 deletions(-) create mode 100644 interface/src/ui/overlays/Planar3DOverlay.cpp create mode 100644 interface/src/ui/overlays/Planar3DOverlay.h create mode 100644 interface/src/ui/overlays/Rectangle3DOverlay.cpp create mode 100644 interface/src/ui/overlays/Rectangle3DOverlay.h diff --git a/examples/editModels.js b/examples/editModels.js index fe520235f5..4be9cbf9f4 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1582,13 +1582,37 @@ var SelectionDisplay = function (opts) { visible: false }); + var baseOverlayAngles = { x: 0, y: 0, z: 0 }; + var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); + + var baseOfEntityOverlay = Overlays.addOverlay("rectangle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 0, green: 0, blue: 0}, + alpha: 1, + solid: false, + visible: false, + rotation: baseOverlayRotation + }); + + var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 51, green: 152, blue: 203 }, + alpha: 0.5, + solid: true, + visible: false, + rotation: baseOverlayRotation + }); + + var yawOverlayAngles = { x: 90, y: 0, z: 0 }; var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); var yawOverlayInner = Overlays.addOverlay("circle3d", { position: { x:0, y: 0, z: 0}, size: 1, - color: { red: 0, green: 195, blue: 255}, - alpha: 0.1, + color: { red: 51, green: 152, blue: 203 }, + alpha: 0.2, solid: true, visible: false, rotation: yawOverlayRotation @@ -1597,8 +1621,8 @@ var SelectionDisplay = function (opts) { var yawOverlayOuter = Overlays.addOverlay("circle3d", { position: { x:0, y: 0, z: 0}, size: 1, - color: { red: 0, green: 195, blue: 215}, - alpha: 0.1, + color: { red: 51, green: 152, blue: 203 }, + alpha: 0.2, solid: true, visible: false, rotation: yawOverlayRotation @@ -1607,10 +1631,9 @@ var SelectionDisplay = function (opts) { var yawOverlayCurrent = Overlays.addOverlay("circle3d", { position: { x:0, y: 0, z: 0}, size: 1, - color: { red: 255, green: 190, blue: 190}, - alpha: 1, - solid: false, - isDashedLine: true, + color: { red: 224, green: 67, blue: 36}, + alpha: 0.8, + solid: true, visible: false, rotation: yawOverlayRotation, }); @@ -1681,6 +1704,8 @@ var SelectionDisplay = function (opts) { this.cleanup = function () { Overlays.deleteOverlay(selectionBox); + Overlays.deleteOverlay(baseOfEntityOverlay); + Overlays.deleteOverlay(baseOfEntityProjectionOverlay); Overlays.deleteOverlay(yawOverlayInner); Overlays.deleteOverlay(yawOverlayOuter); Overlays.deleteOverlay(yawOverlayCurrent); @@ -1694,12 +1719,21 @@ var SelectionDisplay = function (opts) { this.showSelection = function (properties) { var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); var innerRadius = diagonal; var outerRadius = diagonal * 1.15; + var innerActive = false; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + if (innerActive) { + innerAlpha = 0.5; + } else { + outerAlpha = 0.5; + } Overlays.editOverlay(selectionBox, { - visible: true, + visible: false, solid:false, lineWidth: 2.0, position: { x: properties.position.x, @@ -1715,25 +1749,51 @@ var SelectionDisplay = function (opts) { glowLevelPulse: 1.0, alphaPulse: 0.5, colorPulse: -0.5 - }); + Overlays.editOverlay(baseOfEntityOverlay, + { + visible: true, + solid:false, + lineWidth: 2.0, + position: { x: properties.position.x, + y: properties.position.y - halfDimensions.y, + z: properties.position.z }, + + dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, + rotation: properties.rotation, + }); + + Overlays.editOverlay(baseOfEntityProjectionOverlay, + { + visible: true, + solid:true, + lineWidth: 2.0, + position: { x: properties.position.x, + y: 0, + z: properties.position.z }, + + dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, + rotation: properties.rotation, + }); + + + Overlays.editOverlay(yawOverlayInner, { visible: true, - lineWidth: 5.0, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), z: properties.position.z}, size: innerRadius, - innerRadius: 0.9 + innerRadius: 0.9, + alpha: innerAlpha }); Overlays.editOverlay(yawOverlayOuter, { visible: true, - lineWidth: 5.0, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), z: properties.position.z}, @@ -1742,12 +1802,12 @@ var SelectionDisplay = function (opts) { innerRadius: 0.9, startAt: 90, endAt: 405, + alpha: outerAlpha }); Overlays.editOverlay(yawOverlayCurrent, { visible: true, - lineWidth: 5.0, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), z: properties.position.z}, @@ -1817,6 +1877,8 @@ var SelectionDisplay = function (opts) { this.hideSelection = function () { Overlays.editOverlay(selectionBox, { visible: false }); + Overlays.editOverlay(baseOfEntityOverlay, { visible: false }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: false }); Overlays.editOverlay(yawOverlayInner, { visible: false }); Overlays.editOverlay(yawOverlayOuter, { visible: false }); Overlays.editOverlay(yawOverlayCurrent, { visible: false }); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 3500f400ab..bf0fa43f35 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -18,10 +18,15 @@ const glm::vec3 DEFAULT_POSITION = glm::vec3(0.0f, 0.0f, 0.0f); const float DEFAULT_LINE_WIDTH = 1.0f; +const bool DEFAULT_IS_SOLID = false; +const bool DEFAULT_IS_DASHED_LINE = false; Base3DOverlay::Base3DOverlay() : _position(DEFAULT_POSITION), - _lineWidth(DEFAULT_LINE_WIDTH) + _lineWidth(DEFAULT_LINE_WIDTH), + _isSolid(DEFAULT_IS_SOLID), + _isDashedLine(DEFAULT_IS_DASHED_LINE), + _rotation() { } @@ -60,4 +65,51 @@ void Base3DOverlay::setProperties(const QScriptValue& properties) { if (properties.property("lineWidth").isValid()) { setLineWidth(properties.property("lineWidth").toVariant().toFloat()); } + + QScriptValue rotation = properties.property("rotation"); + + if (rotation.isValid()) { + glm::quat newRotation; + + // size, scale, dimensions is special, it might just be a single scalar, or it might be a vector, check that here + QScriptValue x = rotation.property("x"); + QScriptValue y = rotation.property("y"); + QScriptValue z = rotation.property("z"); + QScriptValue w = rotation.property("w"); + + + if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { + newRotation.x = x.toVariant().toFloat(); + newRotation.y = y.toVariant().toFloat(); + newRotation.z = z.toVariant().toFloat(); + newRotation.w = w.toVariant().toFloat(); + setRotation(newRotation); + } + } + + if (properties.property("isSolid").isValid()) { + setIsSolid(properties.property("isSolid").toVariant().toBool()); + } + if (properties.property("isFilled").isValid()) { + setIsSolid(properties.property("isSolid").toVariant().toBool()); + } + if (properties.property("isWire").isValid()) { + setIsSolid(!properties.property("isWire").toVariant().toBool()); + } + if (properties.property("solid").isValid()) { + setIsSolid(properties.property("solid").toVariant().toBool()); + } + if (properties.property("filled").isValid()) { + setIsSolid(properties.property("filled").toVariant().toBool()); + } + if (properties.property("wire").isValid()) { + setIsSolid(!properties.property("wire").toVariant().toBool()); + } + + if (properties.property("isDashedLine").isValid()) { + setIsDashedLine(properties.property("isDashedLine").toVariant().toBool()); + } + if (properties.property("dashed").isValid()) { + setIsDashedLine(!properties.property("dashed").toVariant().toBool()); + } } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index ffe73f0023..b2d8a808fc 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -12,6 +12,8 @@ #define hifi_Base3DOverlay_h #include +#include +//#include #include "Overlay.h" @@ -24,17 +26,28 @@ public: // getters const glm::vec3& getPosition() const { return _position; } + const glm::vec3& getCenter() const { return _position; } // TODO: consider implementing registration points in this class float getLineWidth() const { return _lineWidth; } + bool getIsSolid() const { return _isSolid; } + bool getIsDashedLine() const { return _isDashedLine; } + bool getIsSolidLine() const { return !_isDashedLine; } + const glm::quat& getRotation() const { return _rotation; } // setters void setPosition(const glm::vec3& position) { _position = position; } void setLineWidth(float lineWidth) { _lineWidth = lineWidth; } + void setIsSolid(bool isSolid) { _isSolid = isSolid; } + void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } + void setRotation(const glm::quat& value) { _rotation = value; } virtual void setProperties(const QScriptValue& properties); protected: glm::vec3 _position; float _lineWidth; + glm::quat _rotation; + bool _isSolid; + bool _isDashedLine; }; diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index f29fa6ed8d..8c99b587bc 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -56,16 +56,16 @@ void BillboardOverlay::render() { glPushMatrix(); { glTranslatef(_position.x, _position.y, _position.z); + glm::quat rotation; if (_isFacingAvatar) { // rotate about vertical to face the camera - glm::quat rotation = Application::getInstance()->getCamera()->getRotation(); + rotation = Application::getInstance()->getCamera()->getRotation(); rotation *= glm::angleAxis(glm::pi(), glm::vec3(0.0f, 1.0f, 0.0f)); - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); } else { - glm::vec3 axis = glm::axis(_rotation); - glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z); + rotation = getRotation(); } + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); glScalef(_scale, _scale, _scale); if (_billboardTexture) { @@ -140,20 +140,6 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) { _scale = scaleValue.toVariant().toFloat(); } - QScriptValue rotationValue = properties.property("rotation"); - if (rotationValue.isValid()) { - QScriptValue x = rotationValue.property("x"); - QScriptValue y = rotationValue.property("y"); - QScriptValue z = rotationValue.property("z"); - QScriptValue w = rotationValue.property("w"); - if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { - _rotation.x = x.toVariant().toFloat(); - _rotation.y = y.toVariant().toFloat(); - _rotation.z = z.toVariant().toFloat(); - _rotation.w = w.toVariant().toFloat(); - } - } - QScriptValue isFacingAvatarValue = properties.property("isFacingAvatar"); if (isFacingAvatarValue.isValid()) { _isFacingAvatar = isFacingAvatarValue.toVariant().toBool(); diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h index 5efb5767c6..c1731343bf 100644 --- a/interface/src/ui/overlays/BillboardOverlay.h +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -39,8 +39,7 @@ private: QScopedPointer _billboardTexture; QRect _fromImage; // where from in the image to sample - - glm::quat _rotation; + float _scale; bool _isFacingAvatar; }; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 0f1fcb4cb6..8cb65313ce 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -49,7 +49,7 @@ void Circle3DOverlay::render() { glm::vec3 position = getPosition(); glm::vec3 center = getCenter(); - glm::vec3 dimensions = getDimensions(); + glm::vec2 dimensions = getDimensions(); glm::quat rotation = getRotation(); float glowLevel = getGlowLevel(); @@ -65,7 +65,7 @@ void Circle3DOverlay::render() { glPushMatrix(); glm::vec3 positionToCenter = center - position; glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); - glScalef(dimensions.x, dimensions.y, dimensions.z); + glScalef(dimensions.x, dimensions.y, 1.0f); // Create the circle in the coordinates origin float outerRadius = getOuterRadius(); @@ -153,7 +153,7 @@ void Circle3DOverlay::render() { } void Circle3DOverlay::setProperties(const QScriptValue &properties) { - Volume3DOverlay::setProperties(properties); + Planar3DOverlay::setProperties(properties); QScriptValue startAt = properties.property("startAt"); if (startAt.isValid()) { diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index 5e30925a47..cea9db0384 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -11,9 +11,9 @@ #ifndef hifi_Circle3DOverlay_h #define hifi_Circle3DOverlay_h -#include "Volume3DOverlay.h" +#include "Planar3DOverlay.h" -class Circle3DOverlay : public Volume3DOverlay { +class Circle3DOverlay : public Planar3DOverlay { Q_OBJECT public: diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 63af4ab282..4f6c37e96d 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -19,6 +19,7 @@ #include "LocalVoxelsOverlay.h" #include "ModelOverlay.h" #include "Overlays.h" +#include "Rectangle3DOverlay.h" #include "Sphere3DOverlay.h" #include "TextOverlay.h" @@ -154,6 +155,12 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope thisOverlay->setProperties(properties); created = true; is3D = true; + } else if (type == "rectangle3d") { + thisOverlay = new Rectangle3DOverlay(); + thisOverlay->init(_parent); + thisOverlay->setProperties(properties); + created = true; + is3D = true; } else if (type == "line3d") { thisOverlay = new Line3DOverlay(); thisOverlay->init(_parent); diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp new file mode 100644 index 0000000000..42e059c3ca --- /dev/null +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -0,0 +1,76 @@ +// +// Planar3DOverlay.cpp +// interface/src/ui/overlays +// +// 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 this before QGLWidget, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" + +#include +#include +#include + +#include "Planar3DOverlay.h" + +const float DEFAULT_SIZE = 1.0f; + +Planar3DOverlay::Planar3DOverlay() : + _dimensions(glm::vec2(DEFAULT_SIZE, DEFAULT_SIZE)) +{ +} + +Planar3DOverlay::~Planar3DOverlay() { +} + +void Planar3DOverlay::setProperties(const QScriptValue& properties) { + Base3DOverlay::setProperties(properties); + + QScriptValue dimensions = properties.property("dimensions"); + + // if "dimensions" property was not there, check to see if they included aliases: scale + if (!dimensions.isValid()) { + dimensions = properties.property("scale"); + if (!dimensions.isValid()) { + dimensions = properties.property("size"); + } + } + + if (dimensions.isValid()) { + bool validDimensions = false; + glm::vec2 newDimensions; + + QScriptValue x = dimensions.property("x"); + QScriptValue y = dimensions.property("y"); + + if (x.isValid() && y.isValid()) { + newDimensions.x = x.toVariant().toFloat(); + newDimensions.y = y.toVariant().toFloat(); + validDimensions = true; + } else { + QScriptValue width = dimensions.property("width"); + QScriptValue height = dimensions.property("height"); + if (width.isValid() && height.isValid()) { + newDimensions.x = width.toVariant().toFloat(); + newDimensions.y = height.toVariant().toFloat(); + validDimensions = true; + } + } + + // size, scale, dimensions is special, it might just be a single scalar, check that here + if (!validDimensions && dimensions.isNumber()) { + float size = dimensions.toVariant().toFloat(); + newDimensions.x = size; + newDimensions.y = size; + validDimensions = true; + } + + if (validDimensions) { + setDimensions(newDimensions); + } + } +} diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h new file mode 100644 index 0000000000..c1733bc0fb --- /dev/null +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -0,0 +1,45 @@ +// +// Planar3DOverlay.h +// interface/src/ui/overlays +// +// 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_Planar3DOverlay_h +#define hifi_Planar3DOverlay_h + +// include this before QGLWidget, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" + +#include + +#include +#include + +#include "Base3DOverlay.h" + +class Planar3DOverlay : public Base3DOverlay { + Q_OBJECT + +public: + Planar3DOverlay(); + ~Planar3DOverlay(); + + // getters + const glm::vec2& getDimensions() const { return _dimensions; } + + // setters + void setSize(float size) { _dimensions = glm::vec2(size, size); } + void setDimensions(const glm::vec2& value) { _dimensions = value; } + + virtual void setProperties(const QScriptValue& properties); + +protected: + glm::vec2 _dimensions; +}; + + +#endif // hifi_Planar3DOverlay_h diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp new file mode 100644 index 0000000000..b2caaf60a6 --- /dev/null +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -0,0 +1,115 @@ +// +// Rectangle3DOverlay.cpp +// interface/src/ui/overlays +// +// 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 this before QGLWidget, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" + +#include +#include + +#include "Rectangle3DOverlay.h" +#include "renderer/GlowEffect.h" + +Rectangle3DOverlay::Rectangle3DOverlay() { +} + +Rectangle3DOverlay::~Rectangle3DOverlay() { +} + +void Rectangle3DOverlay::render() { + if (!_visible) { + return; // do nothing if we're not visible + } + + float alpha = getAlpha(); + xColor color = getColor(); + const float MAX_COLOR = 255; + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + glDisable(GL_LIGHTING); + + glm::vec3 position = getPosition(); + glm::vec3 center = getCenter(); + glm::vec2 dimensions = getDimensions(); + glm::vec2 halfDimensions = dimensions * 0.5f; + glm::quat rotation = getRotation(); + + float glowLevel = getGlowLevel(); + Glower* glower = NULL; + if (glowLevel > 0.0f) { + glower = new Glower(glowLevel); + } + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + //glScalef(dimensions.x, dimensions.y, 1.0f); + + glLineWidth(_lineWidth); + + // for our overlay, is solid means we draw a solid "filled" rectangle otherwise we just draw a border line... + if (getIsSolid()) { + glBegin(GL_QUADS); + + glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y); + glVertex3f(halfDimensions.x, 0.0f, -halfDimensions.y); + glVertex3f(halfDimensions.x, 0.0f, halfDimensions.y); + glVertex3f(-halfDimensions.x, 0.0f, halfDimensions.y); + + glEnd(); + } else { + if (getIsDashedLine()) { + + // TODO: change this to be dashed! + glBegin(GL_LINE_STRIP); + + glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y); + glVertex3f(halfDimensions.x, 0.0f, -halfDimensions.y); + glVertex3f(halfDimensions.x, 0.0f, halfDimensions.y); + glVertex3f(-halfDimensions.x, 0.0f, halfDimensions.y); + glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y); + + glEnd(); + } else { + glBegin(GL_LINE_STRIP); + + glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y); + glVertex3f(halfDimensions.x, 0.0f, -halfDimensions.y); + glVertex3f(halfDimensions.x, 0.0f, halfDimensions.y); + glVertex3f(-halfDimensions.x, 0.0f, halfDimensions.y); + glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y); + + glEnd(); + } + } + + glPopMatrix(); + glPopMatrix(); + + if (glower) { + delete glower; + } +} + +void Rectangle3DOverlay::setProperties(const QScriptValue &properties) { + Planar3DOverlay::setProperties(properties); +} + + + + + + + + diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.h b/interface/src/ui/overlays/Rectangle3DOverlay.h new file mode 100644 index 0000000000..367db8c4b7 --- /dev/null +++ b/interface/src/ui/overlays/Rectangle3DOverlay.h @@ -0,0 +1,27 @@ +// +// Rectangle3DOverlay.h +// interface/src/ui/overlays +// +// 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_Rectangle3DOverlay_h +#define hifi_Rectangle3DOverlay_h + +#include "Planar3DOverlay.h" + +class Rectangle3DOverlay : public Planar3DOverlay { + Q_OBJECT + +public: + Rectangle3DOverlay(); + ~Rectangle3DOverlay(); + virtual void render(); + virtual void setProperties(const QScriptValue& properties); +}; + + +#endif // hifi_Rectangle3DOverlay_h diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index 04956fae25..4dfeed33a1 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -18,11 +18,9 @@ #include "Volume3DOverlay.h" const float DEFAULT_SIZE = 1.0f; -const bool DEFAULT_IS_SOLID = false; Volume3DOverlay::Volume3DOverlay() : - _dimensions(glm::vec3(DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE)), - _isSolid(DEFAULT_IS_SOLID) + _dimensions(glm::vec3(DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE)) { } @@ -81,45 +79,4 @@ void Volume3DOverlay::setProperties(const QScriptValue& properties) { setDimensions(newDimensions); } } - - QScriptValue rotation = properties.property("rotation"); - - if (rotation.isValid()) { - glm::quat newRotation; - - // size, scale, dimensions is special, it might just be a single scalar, or it might be a vector, check that here - QScriptValue x = rotation.property("x"); - QScriptValue y = rotation.property("y"); - QScriptValue z = rotation.property("z"); - QScriptValue w = rotation.property("w"); - - - if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { - newRotation.x = x.toVariant().toFloat(); - newRotation.y = y.toVariant().toFloat(); - newRotation.z = z.toVariant().toFloat(); - newRotation.w = w.toVariant().toFloat(); - setRotation(newRotation); - } - } - - if (properties.property("isSolid").isValid()) { - setIsSolid(properties.property("isSolid").toVariant().toBool()); - } - if (properties.property("isWire").isValid()) { - setIsSolid(!properties.property("isWire").toVariant().toBool()); - } - if (properties.property("solid").isValid()) { - setIsSolid(properties.property("solid").toVariant().toBool()); - } - if (properties.property("wire").isValid()) { - setIsSolid(!properties.property("wire").toVariant().toBool()); - } - - if (properties.property("isDashedLine").isValid()) { - setIsDashedLine(properties.property("isDashedLine").toVariant().toBool()); - } - if (properties.property("dashed").isValid()) { - setIsDashedLine(!properties.property("dashed").toVariant().toBool()); - } } diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index 94c261ef2f..35c0567cc5 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -15,8 +15,6 @@ #include "InterfaceConfig.h" #include -#include -#include #include #include @@ -31,28 +29,17 @@ public: ~Volume3DOverlay(); // getters - bool getIsSolid() const { return _isSolid; } - bool getIsDashedLine() const { return _isDashedLine; } - bool getIsSolidLine() const { return !_isDashedLine; } - const glm::vec3& getPosition() const { return _position; } const glm::vec3& getCenter() const { return _position; } // TODO: consider adding registration point!! const glm::vec3& getDimensions() const { return _dimensions; } - const glm::quat& getRotation() const { return _rotation; } // setters void setSize(float size) { _dimensions = glm::vec3(size, size, size); } - void setIsSolid(bool isSolid) { _isSolid = isSolid; } - void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } void setDimensions(const glm::vec3& value) { _dimensions = value; } - void setRotation(const glm::quat& value) { _rotation = value; } virtual void setProperties(const QScriptValue& properties); protected: glm::vec3 _dimensions; - glm::quat _rotation; - bool _isSolid; - bool _isDashedLine; }; From 70847ff3f99ac981eb88c8bc3d25ea102e649103 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 30 Sep 2014 14:53:19 -0700 Subject: [PATCH 009/104] Allow painting boxes that aren't grid-aligned; merged color and material tools. --- interface/src/ui/MetavoxelEditor.cpp | 196 ++++++++++++--------- interface/src/ui/MetavoxelEditor.h | 75 ++------ libraries/networking/src/ResourceCache.cpp | 1 + libraries/networking/src/ResourceCache.h | 5 + 4 files changed, 133 insertions(+), 144 deletions(-) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index f2288a1f97..1adf25448d 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -124,13 +124,10 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new ClearSpannersTool(this)); addTool(new SetSpannerTool(this)); addTool(new HeightfieldHeightBrushTool(this)); - addTool(new HeightfieldColorBrushTool(this)); addTool(new HeightfieldMaterialBrushTool(this)); addTool(new ImportHeightfieldTool(this)); addTool(new EraseHeightfieldTool(this)); - addTool(new VoxelColorBoxTool(this)); addTool(new VoxelMaterialBoxTool(this)); - addTool(new VoxelColorSphereTool(this)); addTool(new VoxelMaterialSphereTool(this)); updateAttributes(); @@ -435,7 +432,7 @@ void BoxTool::render() { glm::quat inverseRotation = glm::inverse(rotation); glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin(); glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection(); - float spacing = _editor->getGridSpacing(); + float spacing = shouldSnapToGrid() ? _editor->getGridSpacing() : 0.0f; float position = _editor->getGridPosition(); if (_state == RAISING_STATE) { // find the plane at the mouse position, orthogonal to the plane, facing the eye position @@ -448,13 +445,13 @@ void BoxTool::render() { if (fabs(divisor) > EPSILON) { float distance = (glm::dot(normal, mousePoint) - glm::dot(normal, rayOrigin)) / divisor; float projection = rayOrigin.z + distance * rayDirection.z; - _height = spacing * roundf(projection / spacing) - position; + _height = (spacing == 0.0f ? projection : spacing * roundf(projection / spacing)) - position; } } else if (fabs(rayDirection.z) > EPSILON) { // find the intersection of the rotated mouse ray with the plane float distance = (position - rayOrigin.z) / rayDirection.z; _mousePosition = glm::vec2(rayOrigin + rayDirection * distance); - glm::vec2 snappedPosition = spacing * glm::floor(_mousePosition / spacing); + glm::vec2 snappedPosition = (spacing == 0.0f) ? _mousePosition : spacing * glm::floor(_mousePosition / spacing); if (_state == HOVERING_STATE) { _startPosition = _endPosition = snappedPosition; @@ -523,7 +520,7 @@ bool BoxTool::eventFilter(QObject* watched, QEvent* event) { float top = base + _height; glm::quat rotation = _editor->getGridRotation(); glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top)); - float spacing = _editor->getGridSpacing(); + float spacing = shouldSnapToGrid() ? _editor->getGridSpacing() : 0.0f; glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) + glm::vec2(spacing, spacing), glm::max(base, top)); @@ -538,6 +535,10 @@ bool BoxTool::eventFilter(QObject* watched, QEvent* event) { return false; } +bool BoxTool::shouldSnapToGrid() { + return true; +} + void BoxTool::resetState() { _state = HOVERING_STATE; _startPosition = INVALID_VECTOR; @@ -1277,20 +1278,11 @@ QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) { alternate ? -_height->value() : _height->value())); } -HeightfieldColorBrushTool::HeightfieldColorBrushTool(MetavoxelEditor* editor) : - HeightfieldBrushTool(editor, "Color Brush") { - - _form->addRow("Color:", _color = new QColorEditor(this)); -} - -QVariant HeightfieldColorBrushTool::createEdit(bool alternate) { - return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), SharedObjectPointer(), - alternate ? QColor() : _color->getColor())); -} - HeightfieldMaterialBrushTool::HeightfieldMaterialBrushTool(MetavoxelEditor* editor) : HeightfieldBrushTool(editor, "Material Brush") { + _form->addRow("Color:", _color = new QColorEditor(this)); + connect(_color, &QColorEditor::colorChanged, this, &HeightfieldMaterialBrushTool::clearTexture); _form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &HeightfieldMaterialBrushTool::updateTexture); } @@ -1300,43 +1292,41 @@ QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) { return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor())); } else { SharedObjectPointer material = _materialEditor->getObject(); - _materialEditor->detachObject(); + if (static_cast(material.data())->getDiffuse().isValid()) { + _materialEditor->detachObject(); + } else { + material = SharedObjectPointer(); + } return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), material, - _texture ? _texture->getAverageColor() : QColor())); + _color->getColor())); } } +void HeightfieldMaterialBrushTool::clearTexture() { + _materialEditor->setObject(new MaterialObject()); +} + void HeightfieldMaterialBrushTool::updateTexture() { + if (_texture) { + _texture->disconnect(this); + } MaterialObject* material = static_cast(_materialEditor->getObject().data()); + if (!material->getDiffuse().isValid()) { + _texture.clear(); + return; + } _texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE); + if (_texture) { + if (_texture->isLoaded()) { + textureLoaded(); + } else { + connect(_texture.data(), &Resource::loaded, this, &HeightfieldMaterialBrushTool::textureLoaded); + } + } } -VoxelColorBoxTool::VoxelColorBoxTool(MetavoxelEditor* editor) : - BoxTool(editor, "Set Voxel Color (Box)", false) { - - QWidget* widget = new QWidget(); - QFormLayout* form = new QFormLayout(); - widget->setLayout(form); - layout()->addWidget(widget); - - form->addRow("Color:", _color = new QColorEditor(this)); -} - -bool VoxelColorBoxTool::appliesTo(const AttributePointer& attribute) const { - return attribute->inherits("VoxelColorAttribute"); -} - -QColor VoxelColorBoxTool::getColor() { - return _color->getColor(); -} - -void VoxelColorBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) { - // ensure that color is either 100% transparent or 100% opaque - QColor color = _color->getColor(); - color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialBoxEdit(Box(minimum, maximum), - _editor->getGridSpacing(), SharedObjectPointer(), color)) }; - Application::getInstance()->getMetavoxels()->applyEdit(message, true); +void HeightfieldMaterialBrushTool::textureLoaded() { + _color->setColor(_texture->getAverageColor()); } VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) : @@ -1347,6 +1337,15 @@ VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) : widget->setLayout(form); layout()->addWidget(widget); + QHBoxLayout* gridLayout = new QHBoxLayout(); + gridLayout->addStretch(1); + gridLayout->addWidget(_snapToGrid = new QCheckBox("Snap to Grid")); + gridLayout->addStretch(1); + form->addRow(gridLayout); + _snapToGrid->setChecked(true); + + form->addRow("Color:", _color = new QColorEditor(this)); + connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialBoxTool::clearTexture); form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialBoxTool::updateTexture); } @@ -1355,26 +1354,53 @@ bool VoxelMaterialBoxTool::appliesTo(const AttributePointer& attribute) const { return attribute->inherits("VoxelColorAttribute"); } +bool VoxelMaterialBoxTool::shouldSnapToGrid() { + return _snapToGrid->isChecked(); +} + QColor VoxelMaterialBoxTool::getColor() { - return _texture ? _texture->getAverageColor() : QColor(); + return _color->getColor(); } void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) { SharedObjectPointer material = _materialEditor->getObject(); - _materialEditor->detachObject(); - QColor color; - if (_texture) { - color = _texture->getAverageColor(); - color.setAlphaF(1.0f); + if (static_cast(material.data())->getDiffuse().isValid()) { + _materialEditor->detachObject(); + } else { + material = SharedObjectPointer(); } + QColor color = _color->getColor(); + color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialBoxEdit(Box(minimum, maximum), _editor->getGridSpacing(), material, color)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } +void VoxelMaterialBoxTool::clearTexture() { + _materialEditor->setObject(new MaterialObject()); +} + void VoxelMaterialBoxTool::updateTexture() { + if (_texture) { + _texture->disconnect(this); + } MaterialObject* material = static_cast(_materialEditor->getObject().data()); + if (!material->getDiffuse().isValid()) { + _texture.clear(); + return; + } _texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE); + if (_texture) { + if (_texture->isLoaded()) { + textureLoaded(); + } else { + connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialBoxTool::textureLoaded); + } + } +} + +void VoxelMaterialBoxTool::textureLoaded() { + _color->setColor(_texture->getAverageColor()); } SphereTool::SphereTool(MetavoxelEditor* editor, const QString& name) : @@ -1436,32 +1462,11 @@ bool SphereTool::eventFilter(QObject* watched, QEvent* event) { return false; } -VoxelColorSphereTool::VoxelColorSphereTool(MetavoxelEditor* editor) : - SphereTool(editor, "Set Voxel Color (Sphere)") { - - _form->addRow("Color:", _color = new QColorEditor(this)); -} - -bool VoxelColorSphereTool::appliesTo(const AttributePointer& attribute) const { - return attribute->inherits("VoxelColorAttribute"); -} - -QColor VoxelColorSphereTool::getColor() { - return _color->getColor(); -} - -void VoxelColorSphereTool::applyValue(const glm::vec3& position, float radius) { - // ensure that color is either 100% transparent or 100% opaque - QColor color = _color->getColor(); - color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSphereEdit(position, radius, - _editor->getGridSpacing(), SharedObjectPointer(), color)) }; - Application::getInstance()->getMetavoxels()->applyEdit(message, true); -} - VoxelMaterialSphereTool::VoxelMaterialSphereTool(MetavoxelEditor* editor) : SphereTool(editor, "Set Voxel Material (Sphere)") { + _form->addRow("Color:", _color = new QColorEditor(this)); + connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialSphereTool::clearTexture); _form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSphereTool::updateTexture); } @@ -1471,23 +1476,46 @@ bool VoxelMaterialSphereTool::appliesTo(const AttributePointer& attribute) const } QColor VoxelMaterialSphereTool::getColor() { - return _texture ? _texture->getAverageColor() : QColor(); + return _color->getColor(); } void VoxelMaterialSphereTool::applyValue(const glm::vec3& position, float radius) { SharedObjectPointer material = _materialEditor->getObject(); - _materialEditor->detachObject(); - QColor color; - if (_texture) { - color = _texture->getAverageColor(); - color.setAlphaF(1.0f); + if (static_cast(material.data())->getDiffuse().isValid()) { + _materialEditor->detachObject(); + } else { + material = SharedObjectPointer(); } + QColor color = _color->getColor(); + color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSphereEdit(position, radius, _editor->getGridSpacing(), material, color)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } -void VoxelMaterialSphereTool::updateTexture() { - MaterialObject* material = static_cast(_materialEditor->getObject().data()); - _texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE); +void VoxelMaterialSphereTool::clearTexture() { + _materialEditor->setObject(new MaterialObject()); +} + +void VoxelMaterialSphereTool::updateTexture() { + if (_texture) { + _texture->disconnect(this); + } + MaterialObject* material = static_cast(_materialEditor->getObject().data()); + if (!material->getDiffuse().isValid()) { + _texture.clear(); + return; + } + _texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE); + if (_texture) { + if (_texture->isLoaded()) { + textureLoaded(); + } else { + connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialSphereTool::textureLoaded); + } + } +} + +void VoxelMaterialSphereTool::textureLoaded() { + _color->setColor(_texture->getAverageColor()); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index b5adab4193..bb8d05f330 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -125,6 +125,8 @@ public: protected: + virtual bool shouldSnapToGrid(); + virtual QColor getColor() = 0; virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum) = 0; @@ -375,23 +377,6 @@ private: QDoubleSpinBox* _height; }; -/// Allows coloring parts of the heightfield. -class HeightfieldColorBrushTool : public HeightfieldBrushTool { - Q_OBJECT - -public: - - HeightfieldColorBrushTool(MetavoxelEditor* editor); - -protected: - - virtual QVariant createEdit(bool alternate); - -private: - - QColorEditor* _color; -}; - /// Allows texturing parts of the heightfield. class HeightfieldMaterialBrushTool : public HeightfieldBrushTool { Q_OBJECT @@ -406,33 +391,15 @@ protected: private slots: + void clearTexture(); void updateTexture(); + void textureLoaded(); -private: - - SharedObjectEditor* _materialEditor; - QSharedPointer _texture; -}; - -/// Allows setting voxel colors by dragging out a box. -class VoxelColorBoxTool : public BoxTool { - Q_OBJECT - -public: - - VoxelColorBoxTool(MetavoxelEditor* editor); - - virtual bool appliesTo(const AttributePointer& attribute) const; - -protected: - - virtual QColor getColor(); - - virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum); - private: QColorEditor* _color; + SharedObjectEditor* _materialEditor; + QSharedPointer _texture; }; /// Allows setting voxel materials by dragging out a box. @@ -447,16 +414,22 @@ public: protected: + virtual bool shouldSnapToGrid(); + virtual QColor getColor(); virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum); private slots: + void clearTexture(); void updateTexture(); + void textureLoaded(); private: + QCheckBox* _snapToGrid; + QColorEditor* _color; SharedObjectEditor* _materialEditor; QSharedPointer _texture; }; @@ -485,27 +458,6 @@ protected: glm::vec3 _position; }; -/// Allows setting voxel colors by moving a sphere around. -class VoxelColorSphereTool : public SphereTool { - Q_OBJECT - -public: - - VoxelColorSphereTool(MetavoxelEditor* editor); - - virtual bool appliesTo(const AttributePointer& attribute) const; - -protected: - - virtual QColor getColor(); - - virtual void applyValue(const glm::vec3& position, float radius); - -private: - - QColorEditor* _color; -}; - /// Allows setting voxel materials by moving a sphere around. class VoxelMaterialSphereTool : public SphereTool { Q_OBJECT @@ -524,10 +476,13 @@ protected: private slots: + void clearTexture(); void updateTexture(); + void textureLoaded(); private: + QColorEditor* _color; SharedObjectEditor* _materialEditor; QSharedPointer _texture; }; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 5c80854286..bd0f1cae01 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -245,6 +245,7 @@ void Resource::attemptRequest() { void Resource::finishedLoading(bool success) { if (success) { _loaded = true; + emit loaded(); } else { _failedToLoad = true; } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index c3a5974da7..d9520a4e68 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -124,6 +124,11 @@ public: Q_INVOKABLE void allReferencesCleared(); +signals: + + /// Fired when the resource has been loaded. + void loaded(); + protected slots: void attemptRequest(); From 5d2f6b98e2714182c8babe3de3c74aec1a2de98a Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 30 Sep 2014 15:20:24 -0700 Subject: [PATCH 010/104] Added erase buttons, initialize color editor on creation. --- interface/src/ui/MetavoxelEditor.cpp | 26 ++++++++++++++++++++-- interface/src/ui/MetavoxelEditor.h | 2 ++ libraries/metavoxels/src/MetavoxelUtil.cpp | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 1adf25448d..be603983f9 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1344,8 +1344,14 @@ VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) : form->addRow(gridLayout); _snapToGrid->setChecked(true); - form->addRow("Color:", _color = new QColorEditor(this)); + QHBoxLayout* colorLayout = new QHBoxLayout(); + form->addRow("Color:", colorLayout); + colorLayout->addWidget(_color = new QColorEditor(this), 1); connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialBoxTool::clearTexture); + QPushButton* eraseButton = new QPushButton("Erase"); + colorLayout->addWidget(eraseButton); + connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialBoxTool::clearColor); + form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialBoxTool::updateTexture); } @@ -1376,6 +1382,11 @@ void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& Application::getInstance()->getMetavoxels()->applyEdit(message, true); } +void VoxelMaterialBoxTool::clearColor() { + _color->setColor(QColor(0, 0, 0, 0)); + clearTexture(); +} + void VoxelMaterialBoxTool::clearTexture() { _materialEditor->setObject(new MaterialObject()); } @@ -1465,8 +1476,14 @@ bool SphereTool::eventFilter(QObject* watched, QEvent* event) { VoxelMaterialSphereTool::VoxelMaterialSphereTool(MetavoxelEditor* editor) : SphereTool(editor, "Set Voxel Material (Sphere)") { - _form->addRow("Color:", _color = new QColorEditor(this)); + QHBoxLayout* colorLayout = new QHBoxLayout(); + _form->addRow("Color:", colorLayout); + colorLayout->addWidget(_color = new QColorEditor(this), 1); connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialSphereTool::clearTexture); + QPushButton* eraseButton = new QPushButton("Erase"); + colorLayout->addWidget(eraseButton); + connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialSphereTool::clearColor); + _form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSphereTool::updateTexture); } @@ -1493,6 +1510,11 @@ void VoxelMaterialSphereTool::applyValue(const glm::vec3& position, float radius Application::getInstance()->getMetavoxels()->applyEdit(message, true); } +void VoxelMaterialSphereTool::clearColor() { + _color->setColor(QColor(0, 0, 0, 0)); + clearTexture(); +} + void VoxelMaterialSphereTool::clearTexture() { _materialEditor->setObject(new MaterialObject()); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index bb8d05f330..21383ca01f 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -422,6 +422,7 @@ protected: private slots: + void clearColor(); void clearTexture(); void updateTexture(); void textureLoaded(); @@ -476,6 +477,7 @@ protected: private slots: + void clearColor(); void clearTexture(); void updateTexture(); void textureLoaded(); diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index e6b96e97b0..4aff339738 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -490,6 +490,7 @@ QColorEditor::QColorEditor(QWidget* parent) : QWidget(parent) { setLayout(layout); layout->addWidget(_button = new QPushButton()); connect(_button, SIGNAL(clicked()), SLOT(selectColor())); + setColor(QColor()); } void QColorEditor::setColor(const QColor& color) { From 14267e3902d44a6a4e5f04d7b58ed6b9f11687e4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 30 Sep 2014 15:42:33 -0700 Subject: [PATCH 011/104] Layout tweak. --- interface/src/ui/MetavoxelEditor.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index be603983f9..becd70dc28 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1345,7 +1345,8 @@ VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) : _snapToGrid->setChecked(true); QHBoxLayout* colorLayout = new QHBoxLayout(); - form->addRow("Color:", colorLayout); + form->addRow(colorLayout); + colorLayout->addWidget(new QLabel("Color:")); colorLayout->addWidget(_color = new QColorEditor(this), 1); connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialBoxTool::clearTexture); QPushButton* eraseButton = new QPushButton("Erase"); @@ -1477,7 +1478,8 @@ VoxelMaterialSphereTool::VoxelMaterialSphereTool(MetavoxelEditor* editor) : SphereTool(editor, "Set Voxel Material (Sphere)") { QHBoxLayout* colorLayout = new QHBoxLayout(); - _form->addRow("Color:", colorLayout); + _form->addRow(colorLayout); + colorLayout->addWidget(new QLabel("Color:")); colorLayout->addWidget(_color = new QColorEditor(this), 1); connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialSphereTool::clearTexture); QPushButton* eraseButton = new QPushButton("Erase"); From 4dbd2367f4e012c8f913e3569666a54b6d491594 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 30 Sep 2014 17:34:01 -0700 Subject: [PATCH 012/104] Working on shapes for metavoxel edits. --- .../metavoxels/src/MetavoxelMessages.cpp | 20 +++++++++ libraries/metavoxels/src/MetavoxelMessages.h | 42 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 04ed5c5d02..fd528a4f1c 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -1135,3 +1135,23 @@ void VoxelMaterialSphereEdit::apply(MetavoxelData& data, const WeakSharedObjectH VoxelMaterialSphereEditVisitor visitor(center, radius, bounds, granularity, material, averageColor); data.guide(visitor); } + +MetavoxelShape::MetavoxelShape(const glm::vec3& translation, const glm::quat& rotation, float scale) : + translation(translation), + rotation(rotation), + scale(scale) { +} + +MetavoxelShape::~MetavoxelShape() { +} + +MetavoxelSphere::MetavoxelSphere(const glm::vec3& translation, const glm::quat& rotation, float scale) : + MetavoxelShape(translation, rotation, scale) { +} + +MetavoxelBox::MetavoxelBox(const glm::vec3& translation, const glm::quat& rotation, float scale, + float aspectXY, float aspectXZ) : + MetavoxelShape(translation, rotation, scale), + aspectXY(aspectXY), + aspectXZ(aspectXZ) { +} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index fec8e69aaa..abfbe79773 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -282,4 +282,46 @@ public: DECLARE_STREAMABLE_METATYPE(VoxelMaterialSphereEdit) +/// Abstract base class for shapes. +class MetavoxelShape { + STREAMABLE + +public: + + STREAM glm::vec3 translation; + STREAM glm::quat rotation; + STREAM float scale; + + MetavoxelShape(const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f); + virtual ~MetavoxelShape(); +}; + +DECLARE_STREAMABLE_METATYPE(MetavoxelShape) + +// A sphere shape. +class MetavoxelSphere : public MetavoxelShape { + STREAMABLE + +public: + + MetavoxelSphere(const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f); +}; + +DECLARE_STREAMABLE_METATYPE(MetavoxelSphere) + +// A box shape. +class MetavoxelBox : public MetavoxelShape { + STREAMABLE + +public: + + STREAM float aspectXY; + STREAM float aspectXZ; + + MetavoxelBox(const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, + float aspectXY = 1.0f, float aspectXZ = 1.0f); +}; + +DECLARE_STREAMABLE_METATYPE(MetavoxelBox) + #endif // hifi_MetavoxelMessages_h From e65f383b2532ffa0b4a59d8997aa3880b0088aed Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 1 Oct 2014 09:43:03 -0700 Subject: [PATCH 013/104] add localRenderAlpha property to entities --- libraries/entities/src/EntityItem.cpp | 8 +++++++- libraries/entities/src/EntityItem.h | 5 +++++ libraries/entities/src/EntityItemProperties.cpp | 8 ++++++++ libraries/entities/src/EntityItemProperties.h | 5 +++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 86fac0997e..12e79332c3 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -25,6 +25,7 @@ const float EntityItem::IMMORTAL = -1.0f; /// special lifetime which means the entity lives for ever. default lifetime const float EntityItem::DEFAULT_GLOW_LEVEL = 0.0f; +const float EntityItem::DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const float EntityItem::DEFAULT_MASS = 1.0f; const float EntityItem::DEFAULT_LIFETIME = EntityItem::IMMORTAL; const float EntityItem::DEFAULT_DAMPING = 0.5f; @@ -61,8 +62,8 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _position = glm::vec3(0,0,0); _rotation = DEFAULT_ROTATION; _dimensions = DEFAULT_DIMENSIONS; - _glowLevel = DEFAULT_GLOW_LEVEL; + _localRenderAlpha = DEFAULT_LOCAL_RENDER_ALPHA; _mass = DEFAULT_MASS; _velocity = DEFAULT_VELOCITY; _gravity = DEFAULT_GRAVITY; @@ -745,6 +746,7 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getAngularVelocity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignoreForCollisions, getIgnoreForCollisions); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove); @@ -779,6 +781,10 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, setAngularVelocity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); + +qDebug() << "EntityItem::setProperties().... localRenderAlpha:" << getLocalRenderAlpha(); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, setIgnoreForCollisions); SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, setCollisionsWillMove); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 621bd94287..b533db3015 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -153,6 +153,10 @@ public: float getGlowLevel() const { return _glowLevel; } void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; } + static const float DEFAULT_LOCAL_RENDER_ALPHA; + float getLocalRenderAlpha() const { return _localRenderAlpha; } + void setLocalRenderAlpha(float localRenderAlpha) { _localRenderAlpha = localRenderAlpha; } + static const float DEFAULT_MASS; float getMass() const { return _mass; } void setMass(float value) { _mass = value; } @@ -263,6 +267,7 @@ protected: glm::vec3 _dimensions; glm::quat _rotation; float _glowLevel; + float _localRenderAlpha; float _mass; glm::vec3 _velocity; glm::vec3 _gravity; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f54ce274f9..ab2ca9b292 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -67,6 +67,7 @@ EntityItemProperties::EntityItemProperties() : _animationFrameIndex(ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX), _animationFPS(ModelEntityItem::DEFAULT_ANIMATION_FPS), _glowLevel(0.0f), + _localRenderAlpha(1.0f), _naturalDimensions(1.0f, 1.0f, 1.0f), _colorChanged(false), @@ -76,6 +77,7 @@ EntityItemProperties::EntityItemProperties() : _animationFrameIndexChanged(false), _animationFPSChanged(false), _glowLevelChanged(false), + _localRenderAlphaChanged(false), _defaultSettings(true) { @@ -156,6 +158,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex); COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS); COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); + COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions); COPY_PROPERTY_TO_QSCRIPTVALUE(collisionsWillMove); @@ -202,6 +205,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localRenderAlpha, setLocalRenderAlpha); + +qDebug() << "EntityItemProperties::copyFromScriptValue().... localRenderAlpha:" << getLocalRenderAlpha(); + COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions); COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(collisionsWillMove, setCollisionsWillMove); @@ -605,6 +612,7 @@ void EntityItemProperties::markAllChanged() { _animationFrameIndexChanged = true; _animationFPSChanged = true; _glowLevelChanged = true; + _localRenderAlphaChanged = true; } AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 0d993acda9..3c77b63ab6 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -161,6 +161,7 @@ public: bool getAnimationIsPlaying() const { return _animationIsPlaying; } float getAnimationFPS() const { return _animationFPS; } float getGlowLevel() const { return _glowLevel; } + float getLocalRenderAlpha() const { return _localRenderAlpha; } const QString& getScript() const { return _script; } // model related properties @@ -171,6 +172,7 @@ public: void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; } void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; } void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; } + void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } void setScript(const QString& value) { _script = value; _scriptChanged = true; } @@ -201,6 +203,7 @@ public: bool animationFrameIndexChanged() const { return _animationFrameIndexChanged; } bool animationFPSChanged() const { return _animationFPSChanged; } bool glowLevelChanged() const { return _glowLevelChanged; } + bool localRenderAlphaChanged() const { return _localRenderAlphaChanged; } void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; } void markAllChanged(); @@ -283,6 +286,7 @@ private: float _animationFrameIndex; float _animationFPS; float _glowLevel; + float _localRenderAlpha; QVector _sittingPoints; glm::vec3 _naturalDimensions; @@ -293,6 +297,7 @@ private: bool _animationFrameIndexChanged; bool _animationFPSChanged; bool _glowLevelChanged; + bool _localRenderAlphaChanged; bool _defaultSettings; }; From 7a9be9e5b9bf711606a02f89f79f11090e901a65 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 1 Oct 2014 09:43:59 -0700 Subject: [PATCH 014/104] implement support for localRenderAlpha property in the rendering portion of entities --- interface/src/entities/RenderableBoxEntityItem.cpp | 10 +++++++--- interface/src/entities/RenderableModelEntityItem.cpp | 4 ++-- interface/src/entities/RenderableSphereEntityItem.cpp | 5 ++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/interface/src/entities/RenderableBoxEntityItem.cpp b/interface/src/entities/RenderableBoxEntityItem.cpp index c39fb222bf..b6363c81b1 100644 --- a/interface/src/entities/RenderableBoxEntityItem.cpp +++ b/interface/src/entities/RenderableBoxEntityItem.cpp @@ -39,9 +39,12 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glm::quat rotation = getRotation(); const bool useGlutCube = true; - + const float MAX_COLOR = 255; + if (useGlutCube) { - glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]); + glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + glPushMatrix(); glTranslatef(position.x, position.y, position.z); glm::vec3 axis = glm::axis(rotation); @@ -84,7 +87,8 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glNormalPointer(GL_FLOAT, 0, normals); glVertexPointer(3, GL_FLOAT, 0, vertices); - glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]); + glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); Application::getInstance()->getDeferredLightingEffect()->bindSimpleProgram(); diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 6eb19bde31..d01b8222ca 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -71,8 +71,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (drawAsModel) { glPushMatrix(); { - const float alpha = 1.0f; - + const float alpha = getLocalRenderAlpha(); + if (!_model || _needsModelReload) { // TODO: this getModel() appears to be about 3% of model render time. We should optimize PerformanceTimer perfTimer("getModel"); diff --git a/interface/src/entities/RenderableSphereEntityItem.cpp b/interface/src/entities/RenderableSphereEntityItem.cpp index 2ff725ad03..ccf61d3106 100644 --- a/interface/src/entities/RenderableSphereEntityItem.cpp +++ b/interface/src/entities/RenderableSphereEntityItem.cpp @@ -36,7 +36,10 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; glm::quat rotation = getRotation(); - glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]); + const float MAX_COLOR = 255; + glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + glPushMatrix(); glTranslatef(position.x, position.y, position.z); glm::vec3 axis = glm::axis(rotation); From 94c2540db0af315d452c280b20b3247a74063db0 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 1 Oct 2014 09:45:20 -0700 Subject: [PATCH 015/104] add dashed line drawing to Base3DOverlay and 3d line and rectangle overlays --- interface/src/ui/overlays/Base3DOverlay.cpp | 34 ++++++++++++++++++- interface/src/ui/overlays/Base3DOverlay.h | 2 ++ interface/src/ui/overlays/Line3DOverlay.cpp | 8 +++-- .../src/ui/overlays/Rectangle3DOverlay.cpp | 19 +++++------ 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index bf0fa43f35..9c67f56e0b 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -110,6 +110,38 @@ void Base3DOverlay::setProperties(const QScriptValue& properties) { setIsDashedLine(properties.property("isDashedLine").toVariant().toBool()); } if (properties.property("dashed").isValid()) { - setIsDashedLine(!properties.property("dashed").toVariant().toBool()); + setIsDashedLine(properties.property("dashed").toVariant().toBool()); } } + +void Base3DOverlay::drawDashedLine(const glm::vec3& start, const glm::vec3& end) { + + glBegin(GL_LINES); + + // draw each line segment with appropriate gaps + const float dashLength = 0.05f; + const float gapLength = 0.025f; + const float segmentLength = dashLength + gapLength; + float length = glm::distance(start, end); + float segmentCount = length / segmentLength; + int segmentCountFloor = (int)glm::floor(segmentCount); + + glm::vec3 segmentVector = (end - start) / segmentCount; + glm::vec3 dashVector = segmentVector / segmentLength * dashLength; + glm::vec3 gapVector = segmentVector / segmentLength * gapLength; + + glm::vec3 point = start; + glVertex3f(point.x, point.y, point.z); + for (int i = 0; i < segmentCountFloor; i++) { + point += dashVector; + glVertex3f(point.x, point.y, point.z); + + point += gapVector; + glVertex3f(point.x, point.y, point.z); + } + glVertex3f(end.x, end.y, end.z); + + glEnd(); + +} + diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index b2d8a808fc..b6094be08f 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -43,6 +43,8 @@ public: virtual void setProperties(const QScriptValue& properties); protected: + void drawDashedLine(const glm::vec3& start, const glm::vec3& end); + glm::vec3 _position; float _lineWidth; glm::quat _rotation; diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 4f09eb55e9..b35a22d4d8 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -40,10 +40,14 @@ void Line3DOverlay::render() { const float MAX_COLOR = 255; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); - glBegin(GL_LINES); + if (getIsDashedLine()) { + drawDashedLine(_position, _end); + } else { + glBegin(GL_LINES); glVertex3f(_position.x, _position.y, _position.z); glVertex3f(_end.x, _end.y, _end.z); - glEnd(); + glEnd(); + } glEnable(GL_LIGHTING); if (glower) { diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index b2caaf60a6..fd41aa012c 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -70,17 +70,17 @@ void Rectangle3DOverlay::render() { glEnd(); } else { if (getIsDashedLine()) { - - // TODO: change this to be dashed! - glBegin(GL_LINE_STRIP); - glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y); - glVertex3f(halfDimensions.x, 0.0f, -halfDimensions.y); - glVertex3f(halfDimensions.x, 0.0f, halfDimensions.y); - glVertex3f(-halfDimensions.x, 0.0f, halfDimensions.y); - glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y); + glm::vec3 point1(-halfDimensions.x, 0.0f, -halfDimensions.y); + glm::vec3 point2(halfDimensions.x, 0.0f, -halfDimensions.y); + glm::vec3 point3(halfDimensions.x, 0.0f, halfDimensions.y); + glm::vec3 point4(-halfDimensions.x, 0.0f, halfDimensions.y); - glEnd(); + drawDashedLine(point1, point2); + drawDashedLine(point2, point3); + drawDashedLine(point3, point4); + drawDashedLine(point4, point1); + } else { glBegin(GL_LINE_STRIP); @@ -112,4 +112,3 @@ void Rectangle3DOverlay::setProperties(const QScriptValue &properties) { - From 060a690f85aebb6ac23cdf84c030517aeacf3a4f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 1 Oct 2014 09:46:34 -0700 Subject: [PATCH 016/104] moved the selection display class into own file --- examples/editModels.js | 342 ++------------------------------ examples/entitySelectionTool.js | 323 ++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 330 deletions(-) create mode 100644 examples/entitySelectionTool.js diff --git a/examples/editModels.js b/examples/editModels.js index 4be9cbf9f4..25c69f93f5 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -23,6 +23,8 @@ // Script.include("toolBars.js"); +Script.include("entitySelectionTool.js"); +var selectionDisplay = SelectionDisplay; var windowDimensions = Controller.getViewportDimensions(); var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; @@ -1571,327 +1573,7 @@ var ExportMenu = function (opts) { Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); }; -var SelectionDisplay = function (opts) { - var self = this; - var selectionBox = Overlays.addOverlay("cube", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 180, green: 180, blue: 180}, - alpha: 1, - solid: false, - visible: false - }); - var baseOverlayAngles = { x: 0, y: 0, z: 0 }; - var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); - - var baseOfEntityOverlay = Overlays.addOverlay("rectangle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 0, green: 0, blue: 0}, - alpha: 1, - solid: false, - visible: false, - rotation: baseOverlayRotation - }); - - var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 51, green: 152, blue: 203 }, - alpha: 0.5, - solid: true, - visible: false, - rotation: baseOverlayRotation - }); - - - var yawOverlayAngles = { x: 90, y: 0, z: 0 }; - var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); - var yawOverlayInner = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 51, green: 152, blue: 203 }, - alpha: 0.2, - solid: true, - visible: false, - rotation: yawOverlayRotation - }); - - var yawOverlayOuter = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 51, green: 152, blue: 203 }, - alpha: 0.2, - solid: true, - visible: false, - rotation: yawOverlayRotation - }); - - var yawOverlayCurrent = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 224, green: 67, blue: 36}, - alpha: 0.8, - solid: true, - visible: false, - rotation: yawOverlayRotation, - }); - - var yawHandleAngles = { x: 90, y: 90, z: 0 }; - var yawHandleRotation = Quat.fromVec3Degrees(yawHandleAngles); - var yawHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", - position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 255 }, - alpha: 0.3, - visible: false, - size: 0.1, - scale: 0.1, - rotation: yawHandleRotation, - isFacingAvatar: false - }); - - var pitchOverlayAngles = { x: 0, y: 90, z: 0 }; - var pitchOverlayRotation = Quat.fromVec3Degrees(pitchOverlayAngles); - var pitchOverlay = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 0, green: 255, blue: 0}, - alpha: 1, - visible: false, - rotation: pitchOverlayRotation - }); - - var pitchHandleAngles = { x: 90, y: 0, z: 90 }; - var pitchHandleRotation = Quat.fromVec3Degrees(pitchHandleAngles); - var pitchHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", - position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 255 }, - alpha: 0.3, - visible: false, - size: 0.1, - scale: 0.1, - rotation: pitchHandleRotation, - isFacingAvatar: false - }); - - var rollOverlayAngles = { x: 0, y: 180, z: 0 }; - var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles); - var rollOverlay = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 255, green: 0, blue: 0}, - alpha: 1, - visible: false, - rotation: rollOverlayRotation - }); - - var rollHandleAngles = { x: 0, y: 0, z: 180 }; - var rollHandleRotation = Quat.fromVec3Degrees(rollHandleAngles); - var rollHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", - position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 255 }, - alpha: 0.3, - visible: false, - size: 0.1, - scale: 0.1, - rotation: rollHandleRotation, - isFacingAvatar: false - }); - - this.cleanup = function () { - Overlays.deleteOverlay(selectionBox); - Overlays.deleteOverlay(baseOfEntityOverlay); - Overlays.deleteOverlay(baseOfEntityProjectionOverlay); - Overlays.deleteOverlay(yawOverlayInner); - Overlays.deleteOverlay(yawOverlayOuter); - Overlays.deleteOverlay(yawOverlayCurrent); - Overlays.deleteOverlay(pitchOverlay); - Overlays.deleteOverlay(rollOverlay); - Overlays.deleteOverlay(yawHandle); - Overlays.deleteOverlay(pitchHandle); - Overlays.deleteOverlay(rollHandle); - }; - - this.showSelection = function (properties) { - - var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); - var innerRadius = diagonal; - var outerRadius = diagonal * 1.15; - var innerActive = false; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - if (innerActive) { - innerAlpha = 0.5; - } else { - outerAlpha = 0.5; - } - - Overlays.editOverlay(selectionBox, - { - visible: false, - solid:false, - lineWidth: 2.0, - position: { x: properties.position.x, - y: properties.position.y, - z: properties.position.z }, - - dimensions: properties.dimensions, - rotation: properties.rotation, - - pulseMin: 0.1, - pulseMax: 1.0, - pulsePeriod: 4.0, - glowLevelPulse: 1.0, - alphaPulse: 0.5, - colorPulse: -0.5 - }); - - Overlays.editOverlay(baseOfEntityOverlay, - { - visible: true, - solid:false, - lineWidth: 2.0, - position: { x: properties.position.x, - y: properties.position.y - halfDimensions.y, - z: properties.position.z }, - - dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, - rotation: properties.rotation, - }); - - Overlays.editOverlay(baseOfEntityProjectionOverlay, - { - visible: true, - solid:true, - lineWidth: 2.0, - position: { x: properties.position.x, - y: 0, - z: properties.position.z }, - - dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, - rotation: properties.rotation, - }); - - - - Overlays.editOverlay(yawOverlayInner, - { - visible: true, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - - size: innerRadius, - innerRadius: 0.9, - alpha: innerAlpha - }); - - Overlays.editOverlay(yawOverlayOuter, - { - visible: true, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - - size: outerRadius, - innerRadius: 0.9, - startAt: 90, - endAt: 405, - alpha: outerAlpha - }); - - Overlays.editOverlay(yawOverlayCurrent, - { - visible: true, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - - size: outerRadius, - startAt: 45, - endAt: 90, - innerRadius: 0.9 - }); - - Overlays.editOverlay(yawHandle, - { - visible: true, - position: { x: properties.position.x - (properties.dimensions.x / 2), - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z - (properties.dimensions.z / 2)}, - - //dimensions: properties.dimensions, - //rotation: properties.rotation - }); - - Overlays.editOverlay(pitchOverlay, - { - visible: false, - lineWidth: 5.0, - position: { x: properties.position.x + (properties.dimensions.x / 2), - y: properties.position.y, - z: properties.position.z }, - - //dimensions: properties.dimensions, - size: diagonal, - //rotation: properties.rotation - alpha: 0.5, - }); - - Overlays.editOverlay(pitchHandle, - { - visible: true, - position: { x: properties.position.x + (properties.dimensions.x / 2), - y: properties.position.y + (properties.dimensions.y / 2), - z: properties.position.z - (properties.dimensions.z / 2)}, - }); - - Overlays.editOverlay(rollOverlay, - { - visible: false, - lineWidth: 5.0, - position: { x: properties.position.x, - y: properties.position.y, - z: properties.position.z + (properties.dimensions.z / 2) }, - - //dimensions: properties.dimensions, - size: diagonal, - //rotation: properties.rotation - alpha: 0.5, - }); - - Overlays.editOverlay(rollHandle, - { - visible: true, - position: { x: properties.position.x - (properties.dimensions.x / 2), - y: properties.position.y + (properties.dimensions.y / 2), - z: properties.position.z + (properties.dimensions.z / 2)}, - }); - - }; - - this.hideSelection = function () { - Overlays.editOverlay(selectionBox, { visible: false }); - Overlays.editOverlay(baseOfEntityOverlay, { visible: false }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: false }); - Overlays.editOverlay(yawOverlayInner, { visible: false }); - Overlays.editOverlay(yawOverlayOuter, { visible: false }); - Overlays.editOverlay(yawOverlayCurrent, { visible: false }); - Overlays.editOverlay(pitchOverlay, { visible: false }); - Overlays.editOverlay(rollOverlay, { visible: false }); - Overlays.editOverlay(yawHandle, { visible: false }); - Overlays.editOverlay(pitchHandle, { visible: false }); - Overlays.editOverlay(rollHandle, { visible: false }); - }; - -}; - -var selectionDisplay = new SelectionDisplay(); var ModelImporter = function (opts) { var self = this; @@ -2340,8 +2022,8 @@ function controller(wichSide) { if (this.glowedIntersectingModel.isKnownID) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); + selectionDisplay.hideSelection(this.glowedIntersectingModel); this.glowedIntersectingModel.isKnownID = false; - selectionDisplay.hideSelection(); } if (!this.grabbing) { var intersection = Entities.findRayIntersection({ @@ -2360,7 +2042,7 @@ function controller(wichSide) { if (wantEntityGlow) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); } - selectionDisplay.showSelection(intersection.properties); + selectionDisplay.showSelection(this.glowedIntersectingModel, intersection.properties); } } } @@ -2431,7 +2113,7 @@ function controller(wichSide) { }); this.oldModelRotation = newRotation; this.oldModelPosition = newPosition; - selectionDisplay.showSelection(Entities.getEntityProperties(this.entityID)); + selectionDisplay.showSelection(this.entityID, Entities.getEntityProperties(this.entityID)); var indicesToRemove = []; for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { @@ -2660,7 +2342,7 @@ function moveEntities() { }); - selectionDisplay.showSelection(Entities.getEntityProperties(leftController.entityID)); + selectionDisplay.showSelection(leftController.entityID, Entities.getEntityProperties(leftController.entityID)); leftController.oldModelPosition = newPosition; leftController.oldModelRotation = rotation; leftController.oldModelHalfDiagonal *= ratio; @@ -2890,7 +2572,7 @@ function mousePressEvent(event) { print("Clicked on " + selectedEntityID.id + " " + entitySelected); tooltip.updateText(selectedEntityProperties); tooltip.show(true); - selectionDisplay.showSelection(selectedEntityProperties); + selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } } @@ -2909,9 +2591,9 @@ function mouseMoveEvent(event) { if (entityIntersection.accurate) { if(glowedEntityID.isKnownID && glowedEntityID.id != entityIntersection.entityID.id) { Entities.editEntity(glowedEntityID, { glowLevel: 0.0 }); + selectionDisplay.hideSelection(glowedEntityID); glowedEntityID.id = -1; glowedEntityID.isKnownID = false; - selectionDisplay.hideSelection(); } var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; @@ -2927,7 +2609,7 @@ function mouseMoveEvent(event) { Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); } glowedEntityID = entityIntersection.entityID; - selectionDisplay.showSelection(entityIntersection.properties); + selectionDisplay.showSelection(entityIntersection.entityID, entityIntersection.properties); } } @@ -3050,7 +2732,7 @@ function mouseMoveEvent(event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); - selectionDisplay.showSelection(selectedEntityProperties); + selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } @@ -3355,7 +3037,7 @@ Controller.keyPressEvent.connect(function (event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); somethingChanged = true; - selectionDisplay.showSelection(selectedEntityProperties); + selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } }); @@ -3473,7 +3155,7 @@ Window.nonBlockingFormClosed.connect(function() { properties.color.blue = array[index++].value; } Entities.editEntity(editModelID, properties); - selectionDisplay.showSelection(propeties); + selectionDisplay.showSelection(editModelID, propeties); } modelSelected = false; }); diff --git a/examples/entitySelectionTool.js b/examples/entitySelectionTool.js new file mode 100644 index 0000000000..917be0416d --- /dev/null +++ b/examples/entitySelectionTool.js @@ -0,0 +1,323 @@ +// +// entitySelectionToolClass.js +// examples +// +// Created by Brad hefta-Gaub on 10/1/14. +// Copyright 2014 High Fidelity, Inc. +// +// This script implements a class useful for building tools for editing entities. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +SelectionDisplay = (function () { + var that = {}; + var selectionBox = Overlays.addOverlay("cube", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 180, green: 180, blue: 180}, + alpha: 1, + solid: false, + visible: false + }); + + var baseOverlayAngles = { x: 0, y: 0, z: 0 }; + var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); + + var baseOfEntityOverlay = Overlays.addOverlay("rectangle3d", { + position: { x:0, y: 0, z: 0}, + color: { red: 0, green: 0, blue: 0}, + alpha: 1, + solid: false, + visible: false, + lineWidth: 2.0, + isDashedLine: true + }); + + var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 51, green: 152, blue: 203 }, + alpha: 0.5, + solid: true, + visible: false, + rotation: baseOverlayRotation + }); + + var heightOfEntityOverlay = Overlays.addOverlay("line3d", { + position: { x:0, y: 0, z: 0}, + end: { x:0, y: 0, z: 0}, + color: { red: 0, green: 0, blue: 0}, + alpha: 1, + solid: false, + visible: false, + lineWidth: 2.0, + isDashedLine: true + }); + + + + var yawOverlayAngles = { x: 90, y: 0, z: 0 }; + var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); + var pitchOverlayAngles = { x: 0, y: 90, z: 0 }; + var pitchOverlayRotation = Quat.fromVec3Degrees(pitchOverlayAngles); + var rollOverlayAngles = { x: 0, y: 180, z: 0 }; + var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles); + + + var rotateOverlayInner = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 51, green: 152, blue: 203 }, + alpha: 0.2, + solid: true, + visible: false, + rotation: yawOverlayRotation + }); + + var rotateOverlayOuter = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 51, green: 152, blue: 203 }, + alpha: 0.2, + solid: true, + visible: false, + rotation: yawOverlayRotation + }); + + var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 224, green: 67, blue: 36}, + alpha: 0.8, + solid: true, + visible: false, + rotation: yawOverlayRotation, + }); + + var yawHandleAngles = { x: 90, y: 90, z: 0 }; + var yawHandleRotation = Quat.fromVec3Degrees(yawHandleAngles); + var yawHandle = Overlays.addOverlay("billboard", { + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + position: { x:0, y: 0, z: 0}, + color: { red: 0, green: 0, blue: 255 }, + alpha: 0.3, + visible: false, + size: 0.1, + scale: 0.1, + rotation: yawHandleRotation, + isFacingAvatar: false + }); + + + var pitchHandleAngles = { x: 90, y: 0, z: 90 }; + var pitchHandleRotation = Quat.fromVec3Degrees(pitchHandleAngles); + var pitchHandle = Overlays.addOverlay("billboard", { + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + position: { x:0, y: 0, z: 0}, + color: { red: 0, green: 0, blue: 255 }, + alpha: 0.3, + visible: false, + size: 0.1, + scale: 0.1, + rotation: pitchHandleRotation, + isFacingAvatar: false + }); + + + var rollHandleAngles = { x: 0, y: 0, z: 180 }; + var rollHandleRotation = Quat.fromVec3Degrees(rollHandleAngles); + var rollHandle = Overlays.addOverlay("billboard", { + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + position: { x:0, y: 0, z: 0}, + color: { red: 0, green: 0, blue: 255 }, + alpha: 0.3, + visible: false, + size: 0.1, + scale: 0.1, + rotation: rollHandleRotation, + isFacingAvatar: false + }); + + that.cleanup = function () { + Overlays.deleteOverlay(selectionBox); + Overlays.deleteOverlay(baseOfEntityOverlay); + Overlays.deleteOverlay(baseOfEntityProjectionOverlay); + Overlays.deleteOverlay(heightOfEntityOverlay); + + Overlays.deleteOverlay(yawHandle); + Overlays.deleteOverlay(pitchHandle); + Overlays.deleteOverlay(rollHandle); + + Overlays.deleteOverlay(rotateOverlayInner); + Overlays.deleteOverlay(rotateOverlayOuter); + Overlays.deleteOverlay(rotateOverlayCurrent); + + }; + + that.showSelection = function (entityID, properties) { + + var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); + var innerRadius = diagonal; + var outerRadius = diagonal * 1.15; + var innerActive = false; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + if (innerActive) { + innerAlpha = 0.5; + } else { + outerAlpha = 0.5; + } + + Overlays.editOverlay(selectionBox, + { + visible: false, + solid:false, + lineWidth: 2.0, + position: { x: properties.position.x, + y: properties.position.y, + z: properties.position.z }, + + dimensions: properties.dimensions, + rotation: properties.rotation, + + pulseMin: 0.1, + pulseMax: 1.0, + pulsePeriod: 4.0, + glowLevelPulse: 1.0, + alphaPulse: 0.5, + colorPulse: -0.5 + }); + + Overlays.editOverlay(baseOfEntityOverlay, + { + visible: true, + position: { x: properties.position.x, + y: properties.position.y - halfDimensions.y, + z: properties.position.z }, + + dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, + rotation: properties.rotation, + }); + + Overlays.editOverlay(baseOfEntityProjectionOverlay, + { + visible: true, + solid:true, + lineWidth: 2.0, + position: { x: properties.position.x, + y: 0, + z: properties.position.z }, + + dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, + rotation: properties.rotation, + }); + + Overlays.editOverlay(heightOfEntityOverlay, + { + visible: true, + position: { x: properties.position.x - halfDimensions.x, + y: properties.position.y - halfDimensions.y, + z: properties.position.z - halfDimensions.z}, + + end: { x: properties.position.x - halfDimensions.x, + y: properties.position.y + halfDimensions.y, + z: properties.position.z - halfDimensions.z}, + + rotation: properties.rotation, + }); + + + Overlays.editOverlay(rotateOverlayInner, + { + visible: true, + position: { x: properties.position.x, + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z}, + + size: innerRadius, + innerRadius: 0.9, + alpha: innerAlpha + }); + + Overlays.editOverlay(rotateOverlayOuter, + { + visible: true, + position: { x: properties.position.x, + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z}, + + size: outerRadius, + innerRadius: 0.9, + startAt: 90, + endAt: 405, + alpha: outerAlpha + }); + + Overlays.editOverlay(rotateOverlayCurrent, + { + visible: true, + position: { x: properties.position.x, + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z}, + + size: outerRadius, + startAt: 45, + endAt: 90, + innerRadius: 0.9 + }); + + Overlays.editOverlay(yawHandle, + { + visible: true, + position: { x: properties.position.x - (properties.dimensions.x / 2), + y: properties.position.y - (properties.dimensions.y / 2), + z: properties.position.z - (properties.dimensions.z / 2)}, + + //dimensions: properties.dimensions, + //rotation: properties.rotation + }); + + Overlays.editOverlay(pitchHandle, + { + visible: true, + position: { x: properties.position.x + (properties.dimensions.x / 2), + y: properties.position.y + (properties.dimensions.y / 2), + z: properties.position.z - (properties.dimensions.z / 2)}, + }); + + Overlays.editOverlay(rollHandle, + { + visible: true, + position: { x: properties.position.x - (properties.dimensions.x / 2), + y: properties.position.y + (properties.dimensions.y / 2), + z: properties.position.z + (properties.dimensions.z / 2)}, + }); + + + Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); + }; + + that.hideSelection = function (entityID) { + Overlays.editOverlay(selectionBox, { visible: false }); + Overlays.editOverlay(baseOfEntityOverlay, { visible: false }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: false }); + Overlays.editOverlay(heightOfEntityOverlay, { visible: false }); + + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); + + Overlays.editOverlay(rotateOverlayInner, { visible: false }); + Overlays.editOverlay(rotateOverlayOuter, { visible: false }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + + Entities.editEntity(entityID, { localRenderAlpha: 1.0 }); + }; + + return that; + +}()); + From a0487e0a57aabacdc27a526242155c340af54921 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 1 Oct 2014 10:42:14 -0700 Subject: [PATCH 017/104] send public and local sockets to data-server in new format --- domain-server/src/DomainServer.cpp | 48 +++++++++++++++++++----------- domain-server/src/DomainServer.h | 7 +++-- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 1ca549cbad..4d7ab8b121 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -157,7 +157,6 @@ bool DomainServer::optionallySetupOAuth() { _oauthProviderURL = DEFAULT_NODE_AUTH_URL; } - // setup our account manager with that _oauthProviderURL AccountManager& accountManager = AccountManager::getInstance(); accountManager.disableSettingsFilePersistence(); accountManager.setAuthURL(_oauthProviderURL); @@ -326,21 +325,14 @@ void DomainServer::setupDynamicSocketUpdating() { // setup our timer to check our IP via stun every 30 seconds QTimer* dynamicIPTimer = new QTimer(this); - connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentIPAddressViaSTUN); + connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); // send public socket changes to the data server so nodes can find us at our new IP - connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer); + connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewSocketsToDataServer); - if (!AccountManager::getInstance().hasValidAccessToken()) { - // we don't have an access token to talk to data-web yet, so - // check our IP address as soon as we get an AccountManager access token - connect(&AccountManager::getInstance(), &AccountManager::loginComplete, - this, &DomainServer::requestCurrentIPAddressViaSTUN); - } else { - // access token good to go, attempt to update our IP now - requestCurrentIPAddressViaSTUN(); - } + // attempt to update our sockets now + requestCurrentPublicSocketViaSTUN(); } else { qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID." @@ -914,18 +906,40 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) { } } -void DomainServer::requestCurrentIPAddressViaSTUN() { +void DomainServer::requestCurrentPublicSocketViaSTUN() { LimitedNodeList::getInstance()->sendSTUNRequest(); } -void DomainServer::sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr) { +QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { + const QString SOCKET_NETWORK_ADDRESS_KEY = "network_address"; + const QString SOCKET_PORT_KEY = "port"; + + QJsonObject socketObject; + socketObject[SOCKET_NETWORK_ADDRESS_KEY] = socket.getAddress().toString(); + socketObject[SOCKET_PORT_KEY] = socket.getPort(); + + return socketObject; +} + +void DomainServer::sendNewSocketsToDataServer(const HifiSockAddr& newPublicSockAddr) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); - // setup the domain object to send to the data server - const QString DOMAIN_JSON_OBJECT = "{\"domain\":{\"network_address\":\"%1\"}}"; + // we're not mointoring for local socket changes yet + // the first time this is called grab whatever it is and assume it'll stay the same + static HifiSockAddr localSockAddr = HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), + LimitedNodeList::getInstance()->getNodeSocket().localPort()); - QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(newPublicSockAddr.getAddress().toString()); + // setup the domain object to send to the data server + const QString PUBLIC_SOCKET_ATTRIBUTES_KEY = "public_socket_attributes"; + const QString LOCAL_SOCKET_ATTRIBUTES_KEY = "local_socket_attributes"; + + QJsonObject domainObject; + domainObject[PUBLIC_SOCKET_ATTRIBUTES_KEY] = jsonForDomainSocketUpdate(newPublicSockAddr); + domainObject[LOCAL_SOCKET_ATTRIBUTES_KEY] = jsonForDomainSocketUpdate(localSockAddr); + + const QString DOMAIN_JSON_OBJECT = "{\"domain\":%1}"; + QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(QString(QJsonDocument(domainObject).toJson())); AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), QNetworkAccessManager::PutOperation, diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 183ce67639..b09602e2e6 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -62,8 +62,8 @@ private slots: void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); - void requestCurrentIPAddressViaSTUN(); - void sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr); + void requestCurrentPublicSocketViaSTUN(); + void sendNewSocketsToDataServer(const HifiSockAddr& newPublicSockAddr); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); @@ -130,7 +130,10 @@ private: QSet _webAuthenticationStateSet; QHash _cookieSessionHash; + HifiSockAddr _localSockAddr; + DomainServerSettingsManager _settingsManager; }; + #endif // hifi_DomainServer_h From d3cef7e592b89aaca69290d761728398180500ed Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 1 Oct 2014 11:57:24 -0700 Subject: [PATCH 018/104] add select for automatic networking to describe settings --- .../resources/describe-settings.json | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 364743110d..fbcbcd41a8 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -12,7 +12,28 @@ "name": "id", "label": "Domain ID", "help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank." - } + }, + { + "name": "automatic_networking", + "label": "Automatic Networking", + "help": "This defines how other nodes in the High Fidelity metaverse will be able to reach your domain-server.
If you don't want to deal with any network settings, use full automatic networking.", + "default": "full", + "type": "select", + "options": [ + { + "value": "full", + "label": "Full: update both the IP address and port to reach my server" + }, + { + "value": "ip", + "label": "IP Only: update just my IP address, I will open the port manually" + }, + { + "value": "none", + "label": "None: use the network information I have entered for this domain at data.highfidelity.io" + } + ] + } ] }, { From 2eec6c1401e82615b18e4e2d7627d4c6ff6987c7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 1 Oct 2014 12:00:10 -0700 Subject: [PATCH 019/104] changes to settings.js to allow for select inputs --- domain-server/resources/web/js/settings.js | 26 +++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index 17365a27a1..4c5c118fef 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -33,9 +33,24 @@ var viewHelpers = { input_type = _.has(setting, 'type') ? setting.type : "text" form_group += ""; - form_group += "" + + if (setting.type === 'select') { + form_group += "" + + form_group += "" + } else { + form_group += "" + } + form_group += "" + setting.help + "" } @@ -90,6 +105,11 @@ $(document).ready(function(){ $('#settings-form').on('click', '#choose-domain-btn', function(){ chooseFromHighFidelityDomains($(this)) }) + + $('#settings-form').on('change', 'select', function(){ + console.log("Changed" + $(this)) + $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change() + }) var panelsSource = $('#panels-template').html() Settings.panelsTemplate = _.template(panelsSource) From d5b0904d7ab814be6ec264df04d2a1642ca85d47 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 1 Oct 2014 12:08:19 -0700 Subject: [PATCH 020/104] handle new automatic networking option in domain-server --- domain-server/src/DomainServer.cpp | 69 ++++++++++++++++++------------ 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 4d7ab8b121..155de22af8 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -306,40 +306,53 @@ bool DomainServer::optionallySetupAssignmentPayment() { } void DomainServer::setupDynamicSocketUpdating() { - const QString ENABLE_DYNAMIC_SOCKET_UPDATING_KEY_PATH = "metaverse.update_sockets"; + const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking"; - const QVariant* updateSocketValue = valueForKeyPath(_settingsManager.getSettingsMap(), - ENABLE_DYNAMIC_SOCKET_UPDATING_KEY_PATH); + const QVariant* automaticNetworkVariant = valueForKeyPath(_settingsManager.getSettingsMap(), + METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH); - if (updateSocketValue && updateSocketValue->canConvert(QMetaType::Bool) && updateSocketValue->toBool() - && didSetupAccountManagerWithAccessToken()) { + const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; + const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; + + if (automaticNetworkVariant) { - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); - const QUuid& domainID = nodeList->getSessionUUID(); - - if (!domainID.isNull()) { - qDebug() << "domain-server socket will be updated for domain with ID" - << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); + QString automaticNetworkValue = automaticNetworkVariant->toString(); + if ((automaticNetworkValue == FULL_AUTOMATIC_NETWORKING_VALUE + || automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE)) { - const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + if (!didSetupAccountManagerWithAccessToken()) { + qDebug() << "Cannot enable domain-server automatic networking without an access token."; + qDebug() << "Please add an access token to your config file or via the web interface."; + + return; + } - // setup our timer to check our IP via stun every 30 seconds - QTimer* dynamicIPTimer = new QTimer(this); - connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); - dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + const QUuid& domainID = nodeList->getSessionUUID(); - // send public socket changes to the data server so nodes can find us at our new IP - connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewSocketsToDataServer); - - // attempt to update our sockets now - requestCurrentPublicSocketViaSTUN(); - - } else { - qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID." - << "Please add an id to your config.json or pass it with the command line argument --id."; - qDebug() << "Failed dynamic IP address update setup. domain-server will now quit."; - - QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + if (!domainID.isNull()) { + qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID" + << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); + + const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + + // setup our timer to check our IP via stun every 30 seconds + QTimer* dynamicIPTimer = new QTimer(this); + connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); + dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); + + // send public socket changes to the data server so nodes can find us at our new IP + connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewSocketsToDataServer); + + // attempt to update our sockets now + requestCurrentPublicSocketViaSTUN(); + + } else { + qDebug() << "Cannot enable domain-server automatic networking without a domain ID." + << "Please add an ID to your config file or via the web interface."; + + return; + } } } } From 84a3f6e8f433596c6b8aec99315da79030989fe0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 1 Oct 2014 15:00:19 -0700 Subject: [PATCH 021/104] Make recalibrate Leap-on-desk if avatar scale changes --- examples/leapHands.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/leapHands.js b/examples/leapHands.js index cc50a328f8..422aed940f 100644 --- a/examples/leapHands.js +++ b/examples/leapHands.js @@ -30,7 +30,9 @@ var leapHands = (function () { CALIBRATED = 2, CALIBRATION_TIME = 1000, // milliseconds PI = 3.141593, - isWindows; + isWindows, + avatarScale, + settingsTimer; function printSkeletonJointNames() { var jointNames, @@ -164,6 +166,8 @@ var leapHands = (function () { calibrationStatus = CALIBRATING; + avatarScale = MyAvatar.scale; + // Set avatar arms vertical, forearms horizontal, as "zero" position for calibration MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, -90.0)); MyAvatar.setJointData("LeftForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0)); @@ -189,6 +193,13 @@ var leapHands = (function () { return false; } + function checkSettings() { + if (!isOnHMD && calibrationStatus && MyAvatar.scale !== avatarScale) { + print("Leap Motion: Recalibrate because avatar scale changed"); + calibrationStatus = UNCALIBRATED; + } + } + function setUp() { // TODO: Leap Motion controller joint naming doesn't match up with skeleton joint naming; numbers are out by 1. @@ -280,6 +291,8 @@ var leapHands = (function () { print("Leap Motion is on desk"); calibrationStatus = UNCALIBRATED; } + + settingsTimer = Script.setInterval(checkSettings, 2000); } function moveHands() { @@ -302,7 +315,7 @@ var leapHands = (function () { if (hands[h].controller.isActive()) { - // Calibrate when and if a controller is first active. + // Calibrate if necessary. if (!checkCalibration()) { return; } @@ -430,6 +443,8 @@ var leapHands = (function () { i, j; + Script.clearInterval(settingsTimer); + for (h = 0; h < NUM_HANDS; h += 1) { Controller.releaseInputController(hands[h].controller); Controller.releaseInputController(wrists[h].controller); From 38b6d5dd5d49bc8a77b14875d5f17ed0954ea9aa Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 1 Oct 2014 16:05:19 -0700 Subject: [PATCH 022/104] Make leapHands.js handle Leap-on-HMD setting changes --- examples/leapHands.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/examples/leapHands.js b/examples/leapHands.js index 422aed940f..c7da249892 100644 --- a/examples/leapHands.js +++ b/examples/leapHands.js @@ -14,6 +14,7 @@ var leapHands = (function () { var isOnHMD, + LEAP_ON_HMD_MENU_ITEM = "Leap Motion on HMD", LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip HMD_OFFSET = 0.100, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief hands, @@ -193,11 +194,33 @@ var leapHands = (function () { return false; } + function setIsOnHMD() { + isOnHMD = Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM); + if (isOnHMD) { + print("Leap Motion: Is on HMD"); + + // Offset of Leap Motion origin from physical eye position + hands[0].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET }; + hands[1].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET }; + + calibrationStatus = CALIBRATED; + } else { + print("Leap Motion: Is on desk"); + calibrationStatus = UNCALIBRATED; + } + } + function checkSettings() { + // There is no "scale changed" event so we need check periodically. if (!isOnHMD && calibrationStatus && MyAvatar.scale !== avatarScale) { print("Leap Motion: Recalibrate because avatar scale changed"); calibrationStatus = UNCALIBRATED; } + + // There is a "menu changed" event but we may as well check here. + if (isOnHMD !== Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM)) { + setIsOnHMD(); + } } function setUp() { @@ -278,19 +301,7 @@ var leapHands = (function () { ] ]; - isOnHMD = Menu.isOptionChecked("Leap Motion on HMD"); - if (isOnHMD) { - print("Leap Motion is on HMD"); - - // Offset of Leap Motion origin from physical eye position - hands[0].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET }; - hands[1].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET }; - - calibrationStatus = CALIBRATED; - } else { - print("Leap Motion is on desk"); - calibrationStatus = UNCALIBRATED; - } + setIsOnHMD(); settingsTimer = Script.setInterval(checkSettings, 2000); } From 3292808342c10598c994a6dd3ce9a8b6d87b9789 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 1 Oct 2014 16:19:10 -0700 Subject: [PATCH 023/104] update domain network address updating for IP auto networking --- domain-server/src/DomainServer.cpp | 93 ++++++++----------- domain-server/src/DomainServer.h | 4 +- .../src/DomainServerSettingsManager.cpp | 36 ++++++- .../src/DomainServerSettingsManager.h | 1 + 4 files changed, 74 insertions(+), 60 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 155de22af8..130bb101dc 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -75,7 +75,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : loadExistingSessionsFromSettings(); // check if we have the flag that enables dynamic IP - setupDynamicSocketUpdating(); + setupAutomaticNetworking(); } } @@ -305,54 +305,48 @@ bool DomainServer::optionallySetupAssignmentPayment() { return true; } -void DomainServer::setupDynamicSocketUpdating() { +void DomainServer::setupAutomaticNetworking() { const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking"; - const QVariant* automaticNetworkVariant = valueForKeyPath(_settingsManager.getSettingsMap(), - METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH); - const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; - if (automaticNetworkVariant) { + QString automaticNetworkValue = + _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); + + if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { + if (!didSetupAccountManagerWithAccessToken()) { + qDebug() << "Cannot enable domain-server automatic networking without an access token."; + qDebug() << "Please add an access token to your config file or via the web interface."; + + return; + } - QString automaticNetworkValue = automaticNetworkVariant->toString(); - if ((automaticNetworkValue == FULL_AUTOMATIC_NETWORKING_VALUE - || automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE)) { + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + const QUuid& domainID = nodeList->getSessionUUID(); + + if (!domainID.isNull()) { + qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID" + << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); - if (!didSetupAccountManagerWithAccessToken()) { - qDebug() << "Cannot enable domain-server automatic networking without an access token."; - qDebug() << "Please add an access token to your config file or via the web interface."; - - return; - } + const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); - const QUuid& domainID = nodeList->getSessionUUID(); + // setup our timer to check our IP via stun every 30 seconds + QTimer* dynamicIPTimer = new QTimer(this); + connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); + dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); - if (!domainID.isNull()) { - qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID" - << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); - - const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; - - // setup our timer to check our IP via stun every 30 seconds - QTimer* dynamicIPTimer = new QTimer(this); - connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); - dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); - - // send public socket changes to the data server so nodes can find us at our new IP - connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewSocketsToDataServer); - - // attempt to update our sockets now - requestCurrentPublicSocketViaSTUN(); - - } else { - qDebug() << "Cannot enable domain-server automatic networking without a domain ID." - << "Please add an ID to your config file or via the web interface."; - - return; - } + // send public socket changes to the data server so nodes can find us at our new IP + connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); + + // attempt to update our sockets now + requestCurrentPublicSocketViaSTUN(); + + } else { + qDebug() << "Cannot enable domain-server automatic networking without a domain ID." + << "Please add an ID to your config file or via the web interface."; + + return; } } } @@ -934,25 +928,16 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { return socketObject; } -void DomainServer::sendNewSocketsToDataServer(const HifiSockAddr& newPublicSockAddr) { +void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); - // we're not mointoring for local socket changes yet - // the first time this is called grab whatever it is and assume it'll stay the same - static HifiSockAddr localSockAddr = HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), - LimitedNodeList::getInstance()->getNodeSocket().localPort()); - // setup the domain object to send to the data server - const QString PUBLIC_SOCKET_ATTRIBUTES_KEY = "public_socket_attributes"; - const QString LOCAL_SOCKET_ATTRIBUTES_KEY = "local_socket_attributes"; + const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; + const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; - QJsonObject domainObject; - domainObject[PUBLIC_SOCKET_ATTRIBUTES_KEY] = jsonForDomainSocketUpdate(newPublicSockAddr); - domainObject[LOCAL_SOCKET_ATTRIBUTES_KEY] = jsonForDomainSocketUpdate(localSockAddr); - - const QString DOMAIN_JSON_OBJECT = "{\"domain\":%1}"; - QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(QString(QJsonDocument(domainObject).toJson())); + const QString DOMAIN_JSON_OBJECT = "{\"domain\": { \"network_address\": \"%1\", \"automatic_networking\": \"ip\" }"; + QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(newPublicSockAddr.getAddress().toString()); AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), QNetworkAccessManager::PutOperation, diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index b09602e2e6..44b215006b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -63,14 +63,14 @@ private slots: void sendPendingTransactionsToServer(); void requestCurrentPublicSocketViaSTUN(); - void sendNewSocketsToDataServer(const HifiSockAddr& newPublicSockAddr); + void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); bool didSetupAccountManagerWithAccessToken(); bool optionallySetupAssignmentPayment(); - void setupDynamicSocketUpdating(); + void setupAutomaticNetworking(); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index b5d7df65cf..8d866a318c 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -26,6 +26,10 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; +const QString DESCRIPTION_SETTINGS_KEY = "settings"; +const QString SETTING_DEFAULT_KEY = "default"; +const QString DESCRIPTION_NAME_KEY = "name"; + DomainServerSettingsManager::DomainServerSettingsManager() : _descriptionArray(), _configMap() @@ -63,6 +67,34 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList } } +QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) { + const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); + + if (foundValue) { + return *foundValue; + } else { + int dotIndex = keyPath.indexOf('.'); + + QString groupKey = keyPath.mid(0, dotIndex); + QString settingKey = keyPath.mid(dotIndex); + + foreach(const QVariant& group, _descriptionArray.toVariantList()) { + QVariantMap groupMap = group.toMap(); + + if (groupMap[DESCRIPTION_NAME_KEY].toString() == groupKey) { + foreach(const QVariant& setting, groupMap[DESCRIPTION_SETTINGS_KEY].toList()) { + QVariantMap settingMap = setting.toMap(); + if (settingMap[DESCRIPTION_NAME_KEY].toString() == settingKey) { + return settingMap[SETTING_DEFAULT_KEY]; + } + } + } + } + } + + return *foundValue; +} + const QString SETTINGS_PATH = "/settings.json"; bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) { @@ -127,10 +159,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection return false; } -const QString DESCRIPTION_SETTINGS_KEY = "settings"; -const QString SETTING_DEFAULT_KEY = "default"; -const QString DESCRIPTION_NAME_KEY = "name"; - QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) { QJsonObject responseObject; diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index b60cb32dfd..c2e8a7d90d 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -26,6 +26,7 @@ public: bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url); void setupConfigMap(const QStringList& argumentList); + QVariant valueOrDefaultValueForKeyPath(const QString& keyPath); QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } private: From d6572c3e2ee88ab8c9acd4132287f4ee917dde9b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 1 Oct 2014 16:38:44 -0700 Subject: [PATCH 024/104] handle automatic networking changes with data-server --- .../resources/describe-settings.json | 2 +- domain-server/src/DomainServer.cpp | 40 ++++++++++++++----- domain-server/src/DomainServer.h | 2 + 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index e1d478753c..303ca7805b 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -29,7 +29,7 @@ "label": "IP Only: update just my IP address, I will open the port manually" }, { - "value": "none", + "value": "disabled", "label": "None: use the network information I have entered for this domain at data.highfidelity.io" } ] diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d6069cac64..3be232dd67 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -74,7 +74,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : loadExistingSessionsFromSettings(); - // check if we have the flag that enables dynamic IP + // setup automatic networking settings with data server setupAutomaticNetworking(); } } @@ -310,22 +310,24 @@ bool DomainServer::optionallySetupAssignmentPayment() { return true; } +const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; +const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; +const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; + void DomainServer::setupAutomaticNetworking() { const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking"; - const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; - const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; + if (!didSetupAccountManagerWithAccessToken()) { + qDebug() << "Cannot setup domain-server automatic networking without an access token."; + qDebug() << "Please add an access token to your config file or via the web interface."; + + return; + } QString automaticNetworkValue = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { - if (!didSetupAccountManagerWithAccessToken()) { - qDebug() << "Cannot enable domain-server automatic networking without an access token."; - qDebug() << "Please add an access token to your config file or via the web interface."; - - return; - } LimitedNodeList* nodeList = LimitedNodeList::getInstance(); const QUuid& domainID = nodeList->getSessionUUID(); @@ -353,6 +355,8 @@ void DomainServer::setupAutomaticNetworking() { return; } + } else { + updateNetworkingInfoWithDataServer(automaticNetworkValue); } } @@ -933,7 +937,13 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { return socketObject; } +const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking"; + void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) { + updateNetworkingInfoWithDataServer(IP_ONLY_AUTOMATIC_NETWORKING_VALUE, newPublicSockAddr.getAddress().toString()); +} + +void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); @@ -941,8 +951,16 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; - const QString DOMAIN_JSON_OBJECT = "{\"domain\": { \"network_address\": \"%1\", \"automatic_networking\": \"ip\" }"; - QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(newPublicSockAddr.getAddress().toString()); + QJsonObject domainObject; + if (!networkAddress.isEmpty()) { + domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress; + } + + qDebug() << "Updating automatic networking setting in domain-server to" << newSetting; + + domainObject[AUTOMATIC_NETWORKING_KEY] = newSetting; + + QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), QNetworkAccessManager::PutOperation, diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 44b215006b..d6846fc610 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -70,7 +70,9 @@ private: bool optionallyReadX509KeyAndCertificate(); bool didSetupAccountManagerWithAccessToken(); bool optionallySetupAssignmentPayment(); + void setupAutomaticNetworking(); + void updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress = QString()); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); From ae90b381727ac8c378044eb2ebce3fe1be9e3512 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 1 Oct 2014 17:19:34 -0700 Subject: [PATCH 025/104] add the ice server --- CMakeLists.txt | 1 + ice-server/CMakeLists.txt | 9 +++++++++ ice-server/src/IceServer.cpp | 18 ++++++++++++++++++ ice-server/src/IceServer.h | 22 ++++++++++++++++++++++ ice-server/src/main.cpp | 27 +++++++++++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 ice-server/CMakeLists.txt create mode 100644 ice-server/src/IceServer.cpp create mode 100644 ice-server/src/IceServer.h create mode 100644 ice-server/src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c0bfb0892..62cdc925f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ endforeach() # targets on all platforms add_subdirectory(assignment-client) add_subdirectory(domain-server) +add_subdirectory(ice-server) add_subdirectory(interface) add_subdirectory(tests) add_subdirectory(tools) diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt new file mode 100644 index 0000000000..c81ba16248 --- /dev/null +++ b/ice-server/CMakeLists.txt @@ -0,0 +1,9 @@ +set(TARGET_NAME ice-server) + +# setup the project and link required Qt modules +setup_hifi_project(Network) + +# link the shared hifi libraries +link_hifi_libraries(networking shared) + +link_shared_dependencies() \ No newline at end of file diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp new file mode 100644 index 0000000000..6543c41c31 --- /dev/null +++ b/ice-server/src/IceServer.cpp @@ -0,0 +1,18 @@ +// +// IceServer.cpp +// ice-server/src +// +// Created by Stephen Birarda on 2014-10-01. +// 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 "IceServer.h" + +IceServer::IceServer(int argc, char* argv[]) : + QCoreApplication(argc, argv) +{ + +} diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h new file mode 100644 index 0000000000..7d56737ca1 --- /dev/null +++ b/ice-server/src/IceServer.h @@ -0,0 +1,22 @@ +// +// IceServer.h +// ice-server/src +// +// Created by Stephen Birarda on 2014-10-01. +// 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_IceServer_h +#define hifi_IceServer_h + +#include + +class IceServer : public QCoreApplication { +public: + IceServer(int argc, char* argv[]); +}; + +#endif // hifi_IceServer_h \ No newline at end of file diff --git a/ice-server/src/main.cpp b/ice-server/src/main.cpp new file mode 100644 index 0000000000..21c8b563b1 --- /dev/null +++ b/ice-server/src/main.cpp @@ -0,0 +1,27 @@ +// +// main.cpp +// ice-server/src +// +// Created by Stephen Birarda on 10/01/12. +// 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 "IceServer.h" + +int main(int argc, char* argv[]) { +#ifndef WIN32 + setvbuf(stdout, NULL, _IOLBF, 0); +#endif + + qInstallMessageHandler(Logging::verboseMessageHandler); + + IceServer iceServer(argc, argv); + return iceServer.exec(); +} \ No newline at end of file From 51f229af570deed0852ff0f08df4a12b246b191c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 1 Oct 2014 18:34:56 -0700 Subject: [PATCH 026/104] Make recalibrate Leap-on-desk if avatar head or skeleton changes --- examples/leapHands.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/leapHands.js b/examples/leapHands.js index c7da249892..1095a9f4dc 100644 --- a/examples/leapHands.js +++ b/examples/leapHands.js @@ -33,6 +33,8 @@ var leapHands = (function () { PI = 3.141593, isWindows, avatarScale, + avatarFaceModelURL, + avatarSkeletonModelURL, settingsTimer; function printSkeletonJointNames() { @@ -168,6 +170,8 @@ var leapHands = (function () { calibrationStatus = CALIBRATING; avatarScale = MyAvatar.scale; + avatarFaceModelURL = MyAvatar.faceModelURL; + avatarSkeletonModelURL = MyAvatar.skeletonModelURL; // Set avatar arms vertical, forearms horizontal, as "zero" position for calibration MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, -90.0)); @@ -212,8 +216,10 @@ var leapHands = (function () { function checkSettings() { // There is no "scale changed" event so we need check periodically. - if (!isOnHMD && calibrationStatus && MyAvatar.scale !== avatarScale) { - print("Leap Motion: Recalibrate because avatar scale changed"); + if (!isOnHMD && calibrationStatus > UNCALIBRATED && (MyAvatar.scale !== avatarScale + || MyAvatar.faceModelURL !== avatarFaceModelURL + || MyAvatar.skeletonModelURL !== avatarSkeletonModelURL)) { + print("Leap Motion: Recalibrate because avatar body or scale changed"); calibrationStatus = UNCALIBRATED; } From 42e95f2b38512443ff83f3e0bbb903fe5e3d909a Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 1 Oct 2014 18:46:23 -0700 Subject: [PATCH 027/104] Working on spanner metavoxel edits. --- interface/src/MetavoxelSystem.cpp | 54 +++-- interface/src/MetavoxelSystem.h | 23 +- interface/src/ui/MetavoxelEditor.cpp | 144 +++++------- interface/src/ui/MetavoxelEditor.h | 42 +--- libraries/metavoxels/src/MetavoxelData.cpp | 85 ++++++- libraries/metavoxels/src/MetavoxelData.h | 65 ++++-- .../metavoxels/src/MetavoxelMessages.cpp | 207 ++++++------------ libraries/metavoxels/src/MetavoxelMessages.h | 54 +---- 8 files changed, 330 insertions(+), 344 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index c307a37eaa..64307593f0 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -33,6 +33,7 @@ REGISTER_META_OBJECT(DefaultMetavoxelRendererImplementation) REGISTER_META_OBJECT(SphereRenderer) +REGISTER_META_OBJECT(CuboidRenderer) REGISTER_META_OBJECT(StaticModelRenderer) static int bufferPointVectorMetaTypeId = qRegisterMetaType(); @@ -2116,7 +2117,8 @@ int SpannerRenderVisitor::visit(MetavoxelInfo& info) { } bool SpannerRenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) { - spanner->getRenderer()->render(1.0f, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize); + const glm::vec4 OPAQUE_WHITE(1.0f, 1.0f, 1.0f, 1.0f); + spanner->getRenderer()->render(OPAQUE_WHITE, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize); return true; } @@ -2282,9 +2284,9 @@ static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { glEnable(plane); } -void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) { +void ClippedRenderer::render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize) { if (clipSize == 0.0f) { - renderUnclipped(alpha, mode); + renderUnclipped(color, mode); return; } enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipSize); @@ -2294,7 +2296,7 @@ void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimu enableClipPlane(GL_CLIP_PLANE4, 0.0f, 0.0f, -1.0f, clipMinimum.z + clipSize); enableClipPlane(GL_CLIP_PLANE5, 0.0f, 0.0f, 1.0f, -clipMinimum.z); - renderUnclipped(alpha, mode); + renderUnclipped(color, mode); glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); @@ -2307,9 +2309,9 @@ void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimu SphereRenderer::SphereRenderer() { } -void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) { +void SphereRenderer::render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize) { if (clipSize == 0.0f) { - renderUnclipped(alpha, mode); + renderUnclipped(color, mode); return; } // slight performance optimization: don't render if clip bounds are entirely within sphere @@ -2318,29 +2320,49 @@ void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum for (int i = 0; i < Box::VERTEX_COUNT; i++) { const float CLIP_PROPORTION = 0.95f; if (glm::distance(sphere->getTranslation(), clipBox.getVertex(i)) >= sphere->getScale() * CLIP_PROPORTION) { - ClippedRenderer::render(alpha, mode, clipMinimum, clipSize); + ClippedRenderer::render(color, mode, clipMinimum, clipSize); return; } } } -void SphereRenderer::renderUnclipped(float alpha, Mode mode) { +void SphereRenderer::renderUnclipped(const glm::vec4& color, Mode mode) { Sphere* sphere = static_cast(_spanner); - const QColor& color = sphere->getColor(); - glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF() * alpha); + const QColor& ownColor = sphere->getColor(); + glColor4f(ownColor.redF() * color.r, ownColor.greenF() * color.g, ownColor.blueF() * color.b, ownColor.alphaF() * color.a); glPushMatrix(); const glm::vec3& translation = sphere->getTranslation(); glTranslatef(translation.x, translation.y, translation.z); glm::quat rotation = sphere->getRotation(); glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); glutSolidSphere(sphere->getScale(), 10, 10); glPopMatrix(); } +CuboidRenderer::CuboidRenderer() { +} + +void CuboidRenderer::renderUnclipped(const glm::vec4& color, Mode mode) { + Cuboid* cuboid = static_cast(_spanner); + const QColor& ownColor = cuboid->getColor(); + glColor4f(ownColor.redF() * color.r, ownColor.greenF() * color.g, ownColor.blueF() * color.b, ownColor.alphaF() * color.a); + + glPushMatrix(); + const glm::vec3& translation = cuboid->getTranslation(); + glTranslatef(translation.x, translation.y, translation.z); + glm::quat rotation = cuboid->getRotation(); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + glutSolidCube(cuboid->getScale() * 2.0f); + + glPopMatrix(); +} + StaticModelRenderer::StaticModelRenderer() : _model(new Model(this)) { } @@ -2374,21 +2396,21 @@ void StaticModelRenderer::simulate(float deltaTime) { _model->simulate(deltaTime); } -void StaticModelRenderer::renderUnclipped(float alpha, Mode mode) { +void StaticModelRenderer::renderUnclipped(const glm::vec4& color, Mode mode) { switch (mode) { case DIFFUSE_MODE: - _model->render(alpha, Model::DIFFUSE_RENDER_MODE); + _model->render(color.a, Model::DIFFUSE_RENDER_MODE); break; case NORMAL_MODE: - _model->render(alpha, Model::NORMAL_RENDER_MODE); + _model->render(color.a, Model::NORMAL_RENDER_MODE); break; default: - _model->render(alpha); + _model->render(color.a); break; } - _model->render(alpha); + _model->render(color.a); } bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index f99a5834c9..301e12473c 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -338,11 +338,11 @@ class ClippedRenderer : public SpannerRenderer { public: - virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize); + virtual void render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize); protected: - virtual void renderUnclipped(float alpha, Mode mode) = 0; + virtual void renderUnclipped(const glm::vec4& color, Mode mode) = 0; }; /// Renders spheres. @@ -353,11 +353,24 @@ public: Q_INVOKABLE SphereRenderer(); - virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize); + virtual void render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize); protected: - virtual void renderUnclipped(float alpha, Mode mode); + virtual void renderUnclipped(const glm::vec4& color, Mode mode); +}; + +/// Renders cuboids. +class CuboidRenderer : public ClippedRenderer { + Q_OBJECT + +public: + + Q_INVOKABLE CuboidRenderer(); + +protected: + + virtual void renderUnclipped(const glm::vec4& color, Mode mode); }; /// Renders static models. @@ -375,7 +388,7 @@ public: protected: - virtual void renderUnclipped(float alpha, Mode mode); + virtual void renderUnclipped(const glm::vec4& color, Mode mode); private slots: diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index becd70dc28..c9f407db09 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -128,7 +128,7 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new ImportHeightfieldTool(this)); addTool(new EraseHeightfieldTool(this)); addTool(new VoxelMaterialBoxTool(this)); - addTool(new VoxelMaterialSphereTool(this)); + addTool(new VoxelMaterialSpannerTool(this)); updateAttributes(); @@ -582,20 +582,21 @@ void GlobalSetTool::apply() { Application::getInstance()->getMetavoxels()->applyEdit(message); } -PlaceSpannerTool::PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, const QString& placeText) : - MetavoxelTool(editor, name) { +PlaceSpannerTool::PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, const QString& placeText, bool usesValue) : + MetavoxelTool(editor, name, usesValue) { - QPushButton* button = new QPushButton(placeText); - layout()->addWidget(button); - connect(button, SIGNAL(clicked()), SLOT(place())); + if (!placeText.isEmpty()) { + QPushButton* button = new QPushButton(placeText); + layout()->addWidget(button); + connect(button, SIGNAL(clicked()), SLOT(place())); + } } void PlaceSpannerTool::simulate(float deltaTime) { if (Application::getInstance()->isMouseHidden()) { return; } - _editor->detachValue(); - Spanner* spanner = static_cast(_editor->getValue().value().data()); + Spanner* spanner = static_cast(getSpanner(true).data()); Transformable* transformable = qobject_cast(spanner); if (transformable) { // find the intersection of the mouse ray with the grid and place the transformable there @@ -615,9 +616,11 @@ void PlaceSpannerTool::render() { if (Application::getInstance()->isMouseHidden()) { return; } - Spanner* spanner = static_cast(_editor->getValue().value().data()); + Spanner* spanner = static_cast(getSpanner().data()); const float SPANNER_ALPHA = 0.25f; - spanner->getRenderer()->render(SPANNER_ALPHA, SpannerRenderer::DEFAULT_MODE, glm::vec3(), 0.0f); + QColor color = getColor(); + spanner->getRenderer()->render(glm::vec4(color.redF(), color.greenF(), color.blueF(), SPANNER_ALPHA), + SpannerRenderer::DEFAULT_MODE, glm::vec3(), 0.0f); } bool PlaceSpannerTool::appliesTo(const AttributePointer& attribute) const { @@ -632,10 +635,21 @@ bool PlaceSpannerTool::eventFilter(QObject* watched, QEvent* event) { return false; } +SharedObjectPointer PlaceSpannerTool::getSpanner(bool detach) { + if (detach) { + _editor->detachValue(); + } + return _editor->getValue().value(); +} + +QColor PlaceSpannerTool::getColor() { + return Qt::white; +} + void PlaceSpannerTool::place() { AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute()); if (attribute) { - applyEdit(attribute, _editor->getValue().value()); + applyEdit(attribute, getSpanner()); } } @@ -904,7 +918,8 @@ void SetSpannerTool::applyEdit(const AttributePointer& attribute, const SharedOb Application::getInstance()->updateUntranslatedViewMatrix(); - spannerData->getRenderer()->render(1.0f, SpannerRenderer::DIFFUSE_MODE, glm::vec3(), 0.0f); + const glm::vec4 OPAQUE_WHITE(1.0f, 1.0f, 1.0f, 1.0f); + spannerData->getRenderer()->render(OPAQUE_WHITE, SpannerRenderer::DIFFUSE_MODE, glm::vec3(), 0.0f); DirectionImages images = { QImage(width, height, QImage::Format_ARGB32), QVector(width * height), minima, maxima, glm::vec3(width / (maxima.x - minima.x), @@ -1415,90 +1430,52 @@ void VoxelMaterialBoxTool::textureLoaded() { _color->setColor(_texture->getAverageColor()); } -SphereTool::SphereTool(MetavoxelEditor* editor, const QString& name) : - MetavoxelTool(editor, name, false, true) { +VoxelMaterialSpannerTool::VoxelMaterialSpannerTool(MetavoxelEditor* editor) : + PlaceSpannerTool(editor, "Set Voxel Material (Spanner)", QString(), false) { QWidget* widget = new QWidget(); - widget->setLayout(_form = new QFormLayout()); + QFormLayout* form = new QFormLayout(); + widget->setLayout(form); layout()->addWidget(widget); - _form->addRow("Radius:", _radius = new QDoubleSpinBox()); - _radius->setSingleStep(0.01); - _radius->setMaximum(FLT_MAX); - _radius->setValue(1.0); -} - -void SphereTool::render() { - if (Application::getInstance()->isMouseHidden()) { - return; - } - glm::quat rotation = _editor->getGridRotation(); - glm::quat inverseRotation = glm::inverse(rotation); - glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin(); - glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection(); - float position = _editor->getGridPosition(); - if (glm::abs(rayDirection.z) < EPSILON) { - return; - } - float distance = (position - rayOrigin.z) / rayDirection.z; - _position = Application::getInstance()->getMouseRayOrigin() + - Application::getInstance()->getMouseRayDirection() * distance; - - glPushMatrix(); - glTranslatef(_position.x, _position.y, _position.z); - - const float CURSOR_ALPHA = 0.5f; - QColor color = getColor(); - glColor4f(color.redF(), color.greenF(), color.blueF(), CURSOR_ALPHA); - - glEnable(GL_CULL_FACE); - - glutSolidSphere(_radius->value(), 10, 10); - - glDisable(GL_CULL_FACE); - - glPopMatrix(); -} - -bool SphereTool::eventFilter(QObject* watched, QEvent* event) { - if (event->type() == QEvent::Wheel) { - float angle = static_cast(event)->angleDelta().y(); - const float ANGLE_SCALE = 1.0f / 1000.0f; - _radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE)); - return true; - - } else if (event->type() == QEvent::MouseButtonPress) { - applyValue(_position, _radius->value()); - return true; - } - return false; -} - -VoxelMaterialSphereTool::VoxelMaterialSphereTool(MetavoxelEditor* editor) : - SphereTool(editor, "Set Voxel Material (Sphere)") { + form->addRow(_spannerEditor = new SharedObjectEditor(&Spanner::staticMetaObject, false, this)); + _spannerEditor->setObject(new Sphere()); QHBoxLayout* colorLayout = new QHBoxLayout(); - _form->addRow(colorLayout); + form->addRow(colorLayout); colorLayout->addWidget(new QLabel("Color:")); colorLayout->addWidget(_color = new QColorEditor(this), 1); - connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialSphereTool::clearTexture); + connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialSpannerTool::clearTexture); QPushButton* eraseButton = new QPushButton("Erase"); colorLayout->addWidget(eraseButton); - connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialSphereTool::clearColor); + connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialSpannerTool::clearColor); - _form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); - connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSphereTool::updateTexture); + form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); + connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSpannerTool::updateTexture); + + QPushButton* place = new QPushButton("Set"); + layout()->addWidget(place); + connect(place, &QPushButton::clicked, this, &VoxelMaterialSpannerTool::place); } -bool VoxelMaterialSphereTool::appliesTo(const AttributePointer& attribute) const { +bool VoxelMaterialSpannerTool::appliesTo(const AttributePointer& attribute) const { return attribute->inherits("VoxelColorAttribute"); } -QColor VoxelMaterialSphereTool::getColor() { +SharedObjectPointer VoxelMaterialSpannerTool::getSpanner(bool detach) { + if (detach) { + _spannerEditor->detachObject(); + } + return _spannerEditor->getObject(); +} + +QColor VoxelMaterialSpannerTool::getColor() { return _color->getColor(); } -void VoxelMaterialSphereTool::applyValue(const glm::vec3& position, float radius) { +void VoxelMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { + _spannerEditor->detachObject(); + SharedObjectPointer material = _materialEditor->getObject(); if (static_cast(material.data())->getDiffuse().isValid()) { _materialEditor->detachObject(); @@ -1507,21 +1484,20 @@ void VoxelMaterialSphereTool::applyValue(const glm::vec3& position, float radius } QColor color = _color->getColor(); color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSphereEdit(position, radius, - _editor->getGridSpacing(), material, color)) }; + MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner, material, color)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } -void VoxelMaterialSphereTool::clearColor() { +void VoxelMaterialSpannerTool::clearColor() { _color->setColor(QColor(0, 0, 0, 0)); clearTexture(); } -void VoxelMaterialSphereTool::clearTexture() { +void VoxelMaterialSpannerTool::clearTexture() { _materialEditor->setObject(new MaterialObject()); } -void VoxelMaterialSphereTool::updateTexture() { +void VoxelMaterialSpannerTool::updateTexture() { if (_texture) { _texture->disconnect(this); } @@ -1535,11 +1511,11 @@ void VoxelMaterialSphereTool::updateTexture() { if (_texture->isLoaded()) { textureLoaded(); } else { - connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialSphereTool::textureLoaded); + connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialSpannerTool::textureLoaded); } } } -void VoxelMaterialSphereTool::textureLoaded() { +void VoxelMaterialSpannerTool::textureLoaded() { _color->setColor(_texture->getAverageColor()); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 21383ca01f..ec92d9de28 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -179,7 +179,8 @@ class PlaceSpannerTool : public MetavoxelTool { public: - PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, const QString& placeText); + PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, + const QString& placeText = QString(), bool usesValue = true); virtual void simulate(float deltaTime); @@ -191,9 +192,11 @@ public: protected: + virtual QColor getColor(); + virtual SharedObjectPointer getSpanner(bool detach = false); virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) = 0; -private slots: +protected slots: void place(); }; @@ -435,45 +438,21 @@ private: QSharedPointer _texture; }; -/// Base class for tools based on a sphere brush. -class SphereTool : public MetavoxelTool { +/// Allows setting voxel materials by placing a spanner. +class VoxelMaterialSpannerTool : public PlaceSpannerTool { Q_OBJECT public: - SphereTool(MetavoxelEditor* editor, const QString& name); - - virtual void render(); - - virtual bool eventFilter(QObject* watched, QEvent* event); - -protected: - - virtual QColor getColor() = 0; - - virtual void applyValue(const glm::vec3& position, float radius) = 0; - - QFormLayout* _form; - QDoubleSpinBox* _radius; - - glm::vec3 _position; -}; - -/// Allows setting voxel materials by moving a sphere around. -class VoxelMaterialSphereTool : public SphereTool { - Q_OBJECT - -public: - - VoxelMaterialSphereTool(MetavoxelEditor* editor); + VoxelMaterialSpannerTool(MetavoxelEditor* editor); virtual bool appliesTo(const AttributePointer& attribute) const; protected: + virtual SharedObjectPointer getSpanner(bool detach = false); virtual QColor getColor(); - - virtual void applyValue(const glm::vec3& position, float radius); + virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner); private slots: @@ -484,6 +463,7 @@ private slots: private: + SharedObjectEditor* _spannerEditor; QColorEditor* _color; SharedObjectEditor* _materialEditor; QSharedPointer _texture; diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 67fafe1633..b71baa2c27 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -15,6 +15,8 @@ #include #include +#include + #include #include "MetavoxelData.h" @@ -29,6 +31,7 @@ REGISTER_META_OBJECT(MetavoxelRenderer) REGISTER_META_OBJECT(DefaultMetavoxelRenderer) REGISTER_META_OBJECT(Spanner) REGISTER_META_OBJECT(Sphere) +REGISTER_META_OBJECT(Cuboid) REGISTER_META_OBJECT(StaticModel) static int metavoxelDataTypeId = registerSimpleMetaType(); @@ -2025,6 +2028,14 @@ bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& dire return _bounds.findRayIntersection(origin, direction, distance); } +bool Spanner::contains(const glm::vec3& point) { + return false; +} + +bool Spanner::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { + return false; +} + QByteArray Spanner::getRendererClassName() const { return "SpannerRendererer"; } @@ -2042,7 +2053,7 @@ void SpannerRenderer::simulate(float deltaTime) { // nothing by default } -void SpannerRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) { +void SpannerRenderer::render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize) { // nothing by default } @@ -2072,20 +2083,22 @@ void Transformable::setScale(float scale) { } } -Sphere::Sphere() : - _color(Qt::gray) { - - connect(this, SIGNAL(translationChanged(const glm::vec3&)), SLOT(updateBounds())); - connect(this, SIGNAL(scaleChanged(float)), SLOT(updateBounds())); - updateBounds(); +ColorTransformable::ColorTransformable() : + _color(Qt::white) { } -void Sphere::setColor(const QColor& color) { +void ColorTransformable::setColor(const QColor& color) { if (_color != color) { emit colorChanged(_color = color); } } +Sphere::Sphere() { + connect(this, SIGNAL(translationChanged(const glm::vec3&)), SLOT(updateBounds())); + connect(this, SIGNAL(scaleChanged(float)), SLOT(updateBounds())); + updateBounds(); +} + const QVector& Sphere::getAttributes() const { static QVector attributes = QVector() << AttributeRegistry::getInstance()->getColorAttribute() << AttributeRegistry::getInstance()->getNormalAttribute(); @@ -2175,6 +2188,38 @@ bool Sphere::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc return findRaySphereIntersection(origin, direction, getTranslation(), getScale(), distance); } +bool Sphere::contains(const glm::vec3& point) { + return glm::distance(point, getTranslation()) <= getScale(); +} + +bool Sphere::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { + glm::vec3 relativeStart = start - getTranslation(); + glm::vec3 vector = end - start; + float a = glm::dot(vector, vector); + if (a == 0.0f) { + return false; + } + float b = glm::dot(relativeStart, vector); + float radicand = b * b - a * (glm::dot(relativeStart, relativeStart) - getScale() * getScale()); + if (radicand < 0.0f) { + return false; + } + float radical = glm::sqrt(radicand); + float first = (-b - radical) / a; + if (first >= 0.0f && first <= 1.0f) { + distance = first; + normal = glm::normalize(relativeStart + vector * distance); + return true; + } + float second = (-b + radical) / a; + if (second >= 0.0f && second <= 1.0f) { + distance = second; + normal = glm::normalize(relativeStart + vector * distance); + return true; + } + return false; +} + QByteArray Sphere::getRendererClassName() const { return "SphereRenderer"; } @@ -2201,6 +2246,30 @@ AttributeValue Sphere::getNormal(MetavoxelInfo& info, int alpha) const { return AttributeValue(getAttributes().at(1), encodeInline(color)); } +Cuboid::Cuboid() { + connect(this, &Cuboid::translationChanged, this, &Cuboid::updateBounds); + connect(this, &Cuboid::rotationChanged, this, &Cuboid::updateBounds); + connect(this, &Cuboid::scaleChanged, this, &Cuboid::updateBounds); + updateBounds(); +} + +bool Cuboid::contains(const glm::vec3& point) { + return false; +} + +bool Cuboid::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { + return false; +} + +QByteArray Cuboid::getRendererClassName() const { + return "CuboidRenderer"; +} + +void Cuboid::updateBounds() { + glm::vec3 extent(getScale(), getScale(), getScale()); + setBounds(glm::translate(getTranslation()) * glm::mat4_cast(getRotation()) * Box(-extent, extent)); +} + StaticModel::StaticModel() { } diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 8308c3c69b..92aeeaa247 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -639,6 +639,12 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& clipMinimum, float clipSize, float& distance) const; + /// Checks whether the spanner contains the specified point. + virtual bool contains(const glm::vec3& point); + + /// Finds the intersection, if any, between the specified line segment and the spanner. + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + signals: void boundsWillChange(); @@ -675,7 +681,7 @@ public: virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); - virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize); + virtual void render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize); virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& clipMinimum, float clipSize, float& distance) const; @@ -717,29 +723,44 @@ private: float _scale; }; -/// A sphere. -class Sphere : public Transformable { +/// A transformable object with a color. +class ColorTransformable : public Transformable { Q_OBJECT - Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged DESIGNABLE false) + +public: + + ColorTransformable(); + + void setColor(const QColor& color); + const QColor& getColor() const { return _color; } +signals: + + void colorChanged(const QColor& color); + +protected: + + QColor _color; +}; + +/// A sphere. +class Sphere : public ColorTransformable { + Q_OBJECT + public: Q_INVOKABLE Sphere(); - void setColor(const QColor& color); - const QColor& getColor() const { return _color; } - virtual const QVector& getAttributes() const; virtual const QVector& getVoxelizedAttributes() const; virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const; virtual bool blendAttributeValues(MetavoxelInfo& info, bool force = false) const; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& clipMinimum, float clipSize, float& distance) const; - -signals: - - void colorChanged(const QColor& color); - + virtual bool contains(const glm::vec3& point); + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + protected: virtual QByteArray getRendererClassName() const; @@ -751,8 +772,26 @@ private slots: private: AttributeValue getNormal(MetavoxelInfo& info, int alpha) const; +}; + +/// A cuboid. +class Cuboid : public ColorTransformable { + Q_OBJECT + +public: - QColor _color; + Q_INVOKABLE Cuboid(); + + virtual bool contains(const glm::vec3& point); + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + +protected: + + virtual QByteArray getRendererClassName() const; + +private slots: + + void updateBounds(); }; /// A static 3D model loaded from the network. diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index fd528a4f1c..7fb605d066 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -849,43 +849,45 @@ void VoxelMaterialBoxEdit::apply(MetavoxelData& data, const WeakSharedObjectHash data.guide(visitor); } -class VoxelMaterialSphereEditVisitor : public MetavoxelVisitor { +VoxelMaterialSpannerEdit::VoxelMaterialSpannerEdit(const SharedObjectPointer& spanner, + const SharedObjectPointer& material, const QColor& averageColor) : + spanner(spanner), + material(material), + averageColor(averageColor) { +} + +class VoxelMaterialSpannerEditVisitor : public MetavoxelVisitor { public: - VoxelMaterialSphereEditVisitor(const glm::vec3& center, float radius, const Box& bounds, float granularity, - const SharedObjectPointer& material, const QColor& color); + VoxelMaterialSpannerEditVisitor(Spanner* spanner, const SharedObjectPointer& material, const QColor& color); virtual int visit(MetavoxelInfo& info); private: - glm::vec3 _center; - float _radius; - Box _bounds; + Spanner* _spanner; SharedObjectPointer _material; QColor _color; float _blockSize; }; -VoxelMaterialSphereEditVisitor::VoxelMaterialSphereEditVisitor(const glm::vec3& center, float radius, const Box& bounds, - float granularity, const SharedObjectPointer& material, const QColor& color) : +VoxelMaterialSpannerEditVisitor::VoxelMaterialSpannerEditVisitor(Spanner* spanner, + const SharedObjectPointer& material, const QColor& color) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << AttributeRegistry::getInstance()->getVoxelMaterialAttribute()), - _center(center), - _radius(radius), - _bounds(bounds), + _spanner(spanner), _material(material), _color(color), - _blockSize(granularity * VOXEL_BLOCK_SIZE) { + _blockSize(spanner->getVoxelizationGranularity() * VOXEL_BLOCK_SIZE) { } -int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { +int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) { Box bounds = info.getBounds(); - if (!bounds.intersects(_bounds)) { + if (!bounds.intersects(_spanner->getBounds())) { return STOP_RECURSION; } if (info.size > _blockSize) { @@ -896,7 +898,7 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { colorPointer->getContents() : QVector(VOXEL_BLOCK_VOLUME); QVector colorContents = oldColorContents; - Box overlap = info.getBounds().getIntersection(_bounds); + Box overlap = info.getBounds().getIntersection(_spanner->getBounds()); float scale = VOXEL_BLOCK_SIZE / info.size; overlap.minimum = (overlap.minimum - info.minimum) * scale; overlap.maximum = (overlap.maximum - info.minimum) * scale; @@ -907,21 +909,18 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { int sizeY = (int)overlap.maximum.y - minY + 1; int sizeZ = (int)overlap.maximum.z - minZ + 1; - glm::vec3 relativeCenter = (_center - info.minimum) * scale; - float relativeRadius = _radius * scale; - float relativeRadiusSquared = relativeRadius * relativeRadius; - QRgb rgb = _color.rgba(); bool flipped = (qAlpha(rgb) == 0); - glm::vec3 position(0.0f, 0.0f, minZ); + float step = 1.0f / scale; + glm::vec3 position(0.0f, 0.0f, info.minimum.z + minZ * step); for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) { - position.y = minY; + *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) { + position.y = info.minimum.y + minY * step; for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; - destY += VOXEL_BLOCK_SAMPLES, position.y++) { - position.x = minX; - for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x++) { - if (glm::distance(relativeCenter, position) <= relativeRadius) { + destY += VOXEL_BLOCK_SAMPLES, position.y += step) { + position.x = info.minimum.x + minX * step; + for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) { + if (_spanner->contains(position)) { *destX = rgb; } } @@ -962,38 +961,26 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { hermiteDestX += VoxelHermiteData::EDGE_COUNT) { // at each intersected non-terminal edge, we check for a transition and, if one is detected, we assign the // crossing and normal values based on intersection with the sphere - glm::vec3 vector(x - relativeCenter.x, y - relativeCenter.y, z - relativeCenter.z); + float distance; + glm::vec3 normal; if (x != VOXEL_BLOCK_SIZE) { int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; const QRgb* color = colorContents.constData() + offset; int alpha0 = qAlpha(color[0]); int alpha1 = qAlpha(color[1]); if (alpha0 != alpha1) { - float radicand = relativeRadiusSquared - vector.y * vector.y - vector.z * vector.z; - float parameter = 0.5f; - if (radicand >= 0.0f) { - float root = glm::sqrt(radicand); - parameter = -vector.x - root; - if (parameter < 0.0f || parameter > 1.0f) { - parameter = glm::clamp(-vector.x + root, 0.0f, 1.0f); + if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step, + info.minimum + glm::vec3(x + 1, y, z) * step, distance, normal)) { + const QRgb* oldColor = oldColorContents.constData() + offset; + if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) { + int alpha = distance * EIGHT_BIT_MAXIMUM; + if (normal.x < 0.0f ? alpha <= qAlpha(hermiteDestX[0]) : alpha >= qAlpha(hermiteDestX[0])) { + hermiteDestX[0] = packNormal(flipped ? -normal : normal, alpha); + } + } else { + hermiteDestX[0] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM); } } - glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f); - float length = glm::length(normal); - if (length > EPSILON) { - normal /= length; - } else { - normal = glm::vec3(0.0f, 1.0f, 0.0f); - } - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) { - int alpha = parameter * EIGHT_BIT_MAXIMUM; - if (normal.x < 0.0f ? alpha <= qAlpha(hermiteDestX[0]) : alpha >= qAlpha(hermiteDestX[0])) { - hermiteDestX[0] = packNormal(flipped ? -normal : normal, alpha); - } - } else { - hermiteDestX[0] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM); - } } else { hermiteDestX[0] = 0x0; } @@ -1006,31 +993,18 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { int alpha0 = qAlpha(color[0]); int alpha2 = qAlpha(color[VOXEL_BLOCK_SAMPLES]); if (alpha0 != alpha2) { - float radicand = relativeRadiusSquared - vector.x * vector.x - vector.z * vector.z; - float parameter = 0.5f; - if (radicand >= 0.0f) { - float root = glm::sqrt(radicand); - parameter = -vector.y - root; - if (parameter < 0.0f || parameter > 1.0f) { - parameter = glm::clamp(-vector.y + root, 0.0f, 1.0f); + if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step, + info.minimum + glm::vec3(x, y + 1, z) * step, distance, normal)) { + const QRgb* oldColor = oldColorContents.constData() + offset; + if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) { + int alpha = distance * EIGHT_BIT_MAXIMUM; + if (normal.y < 0.0f ? alpha <= qAlpha(hermiteDestX[1]) : alpha >= qAlpha(hermiteDestX[1])) { + hermiteDestX[1] = packNormal(flipped ? -normal : normal, alpha); + } + } else { + hermiteDestX[1] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM); } } - glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f); - float length = glm::length(normal); - if (length > EPSILON) { - normal /= length; - } else { - normal = glm::vec3(1.0f, 0.0f, 0.0f); - } - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) { - int alpha = parameter * EIGHT_BIT_MAXIMUM; - if (normal.y < 0.0f ? alpha <= qAlpha(hermiteDestX[1]) : alpha >= qAlpha(hermiteDestX[1])) { - hermiteDestX[1] = packNormal(flipped ? -normal : normal, alpha); - } - } else { - hermiteDestX[1] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM); - } } else { hermiteDestX[1] = 0x0; } @@ -1043,31 +1017,18 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { int alpha0 = qAlpha(color[0]); int alpha4 = qAlpha(color[VOXEL_BLOCK_AREA]); if (alpha0 != alpha4) { - float radicand = relativeRadiusSquared - vector.x * vector.x - vector.y * vector.y; - float parameter = 0.5f; - if (radicand >= 0.0f) { - float root = glm::sqrt(radicand); - parameter = -vector.z - root; - if (parameter < 0.0f || parameter > 1.0f) { - parameter = glm::clamp(-vector.z + root, 0.0f, 1.0f); + if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step, + info.minimum + glm::vec3(x, y, z + 1) * step, distance, normal)) { + const QRgb* oldColor = oldColorContents.constData() + offset; + if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) { + int alpha = distance * EIGHT_BIT_MAXIMUM; + if (normal.z < 0.0f ? alpha <= qAlpha(hermiteDestX[2]) : alpha >= qAlpha(hermiteDestX[2])) { + hermiteDestX[2] = packNormal(flipped ? -normal : normal, alpha); + } + } else { + hermiteDestX[2] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM); } } - glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f); - float length = glm::length(normal); - if (length > EPSILON) { - normal /= length; - } else { - normal = glm::vec3(1.0f, 0.0f, 0.0f); - } - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) { - int alpha = parameter * EIGHT_BIT_MAXIMUM; - if (normal.z < 0.0f ? alpha <= qAlpha(hermiteDestX[2]) : alpha >= qAlpha(hermiteDestX[2])) { - hermiteDestX[2] = packNormal(flipped ? -normal : normal, alpha); - } - } else { - hermiteDestX[2] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM); - } } else { hermiteDestX[2] = 0x0; } @@ -1094,15 +1055,15 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { } uchar materialIndex = getMaterialIndex(_material, materials, materialContents); - position.z = minZ; + position.z = info.minimum.z + minZ * step; for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) { - position.y = minY; + *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) { + position.y = info.minimum.y + minY * step; for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; - destY += VOXEL_BLOCK_SAMPLES, position.y++) { - position.x = minX; - for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x++) { - if (glm::distance(relativeCenter, position) <= relativeRadius) { + destY += VOXEL_BLOCK_SAMPLES, position.y += step) { + position.x = info.minimum.x + minX * step; + for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) { + if (_spanner->contains(position)) { *destX = materialIndex; } } @@ -1115,43 +1076,13 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } - -VoxelMaterialSphereEdit::VoxelMaterialSphereEdit(const glm::vec3& center, float radius, float granularity, - const SharedObjectPointer& material, const QColor& averageColor) : - center(center), - radius(radius), - granularity(granularity), - material(material), - averageColor(averageColor) { -} - -void VoxelMaterialSphereEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + +void VoxelMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { // expand to fit the entire edit - glm::vec3 extents(radius, radius, radius); - Box bounds(center - extents, center + extents); - while (!data.getBounds().contains(bounds)) { + Spanner* spanner = static_cast(this->spanner.data()); + while (!data.getBounds().contains(spanner->getBounds())) { data.expand(); } - VoxelMaterialSphereEditVisitor visitor(center, radius, bounds, granularity, material, averageColor); + VoxelMaterialSpannerEditVisitor visitor(spanner, material, averageColor); data.guide(visitor); } - -MetavoxelShape::MetavoxelShape(const glm::vec3& translation, const glm::quat& rotation, float scale) : - translation(translation), - rotation(rotation), - scale(scale) { -} - -MetavoxelShape::~MetavoxelShape() { -} - -MetavoxelSphere::MetavoxelSphere(const glm::vec3& translation, const glm::quat& rotation, float scale) : - MetavoxelShape(translation, rotation, scale) { -} - -MetavoxelBox::MetavoxelBox(const glm::vec3& translation, const glm::quat& rotation, float scale, - float aspectXY, float aspectXZ) : - MetavoxelShape(translation, rotation, scale), - aspectXY(aspectXY), - aspectXZ(aspectXZ) { -} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index abfbe79773..c94eaf511d 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -262,66 +262,22 @@ public: DECLARE_STREAMABLE_METATYPE(VoxelMaterialBoxEdit) -/// An edit that sets the materials of voxels within a sphere to a value. -class VoxelMaterialSphereEdit : public MetavoxelEdit { +/// An edit that sets the materials of voxels within a spanner to a value. +class VoxelMaterialSpannerEdit : public MetavoxelEdit { STREAMABLE public: - STREAM glm::vec3 center; - STREAM float radius; - STREAM float granularity; + STREAM SharedObjectPointer spanner; STREAM SharedObjectPointer material; STREAM QColor averageColor; - VoxelMaterialSphereEdit(const glm::vec3& center = glm::vec3(), float radius = 0.0f, float granularity = 0.0f, + VoxelMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(), const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor()); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; -DECLARE_STREAMABLE_METATYPE(VoxelMaterialSphereEdit) - -/// Abstract base class for shapes. -class MetavoxelShape { - STREAMABLE - -public: - - STREAM glm::vec3 translation; - STREAM glm::quat rotation; - STREAM float scale; - - MetavoxelShape(const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f); - virtual ~MetavoxelShape(); -}; - -DECLARE_STREAMABLE_METATYPE(MetavoxelShape) - -// A sphere shape. -class MetavoxelSphere : public MetavoxelShape { - STREAMABLE - -public: - - MetavoxelSphere(const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f); -}; - -DECLARE_STREAMABLE_METATYPE(MetavoxelSphere) - -// A box shape. -class MetavoxelBox : public MetavoxelShape { - STREAMABLE - -public: - - STREAM float aspectXY; - STREAM float aspectXZ; - - MetavoxelBox(const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, - float aspectXY = 1.0f, float aspectXZ = 1.0f); -}; - -DECLARE_STREAMABLE_METATYPE(MetavoxelBox) +DECLARE_STREAMABLE_METATYPE(VoxelMaterialSpannerEdit) #endif // hifi_MetavoxelMessages_h From e0a721209a18a77325e53bd638e79eb56f07fb9c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 09:55:00 -0700 Subject: [PATCH 028/104] add a Node superclass to handle P2P connection --- libraries/networking/src/NetworkPeer.cpp | 66 ++++++++++++++++++++++++ libraries/networking/src/NetworkPeer.h | 46 +++++++++++++++++ libraries/networking/src/Node.cpp | 48 +---------------- libraries/networking/src/Node.h | 22 ++------ 4 files changed, 116 insertions(+), 66 deletions(-) create mode 100644 libraries/networking/src/NetworkPeer.cpp create mode 100644 libraries/networking/src/NetworkPeer.h diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp new file mode 100644 index 0000000000..bb26b2a119 --- /dev/null +++ b/libraries/networking/src/NetworkPeer.cpp @@ -0,0 +1,66 @@ +// +// NetworkPeer.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-10-02. +// 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 "NetworkPeer.h" + +NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : + _uuid(uuid), + _publicSocket(publicSocket), + _localSocket(localSocket), + _symmetricSocket(), + _activeSocket(NULL) +{ + +} + +void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { + if (_activeSocket == &_publicSocket) { + // if the active socket was the public socket then reset it to NULL + _activeSocket = NULL; + } + + _publicSocket = publicSocket; +} + +void NetworkPeer::setLocalSocket(const HifiSockAddr& localSocket) { + if (_activeSocket == &_localSocket) { + // if the active socket was the local socket then reset it to NULL + _activeSocket = NULL; + } + + _localSocket = localSocket; +} + +void NetworkPeer::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { + if (_activeSocket == &_symmetricSocket) { + // if the active socket was the symmetric socket then reset it to NULL + _activeSocket = NULL; + } + + _symmetricSocket = symmetricSocket; +} + +void NetworkPeer::activateLocalSocket() { + qDebug() << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + _activeSocket = &_localSocket; +} + +void NetworkPeer::activatePublicSocket() { + qDebug() << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + _activeSocket = &_publicSocket; +} + +void NetworkPeer::activateSymmetricSocket() { + qDebug() << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + _activeSocket = &_symmetricSocket; +} \ No newline at end of file diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h new file mode 100644 index 0000000000..b4e472b80c --- /dev/null +++ b/libraries/networking/src/NetworkPeer.h @@ -0,0 +1,46 @@ +// +// NetworkPeer.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-10-02. +// 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_NetworkPeer_h +#define hifi_NetworkPeer_h + +#include +#include + +#include "HifiSockAddr.h" + +class NetworkPeer : public QObject { +public: + NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); + + const HifiSockAddr& getPublicSocket() const { return _publicSocket; } + void setPublicSocket(const HifiSockAddr& publicSocket); + const HifiSockAddr& getLocalSocket() const { return _localSocket; } + void setLocalSocket(const HifiSockAddr& localSocket); + const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } + void setSymmetricSocket(const HifiSockAddr& symmetricSocket); + + const HifiSockAddr* getActiveSocket() const { return _activeSocket; } + + void activatePublicSocket(); + void activateLocalSocket(); + void activateSymmetricSocket(); + +protected: + QUuid _uuid; + + HifiSockAddr _publicSocket; + HifiSockAddr _localSocket; + HifiSockAddr _symmetricSocket; + HifiSockAddr* _activeSocket; +}; + +#endif // hifi_NetworkPeer_h \ No newline at end of file diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 662c827069..942f911f23 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -44,14 +44,10 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { } Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : + NetworkPeer(uuid, publicSocket, localSocket), _type(type), - _uuid(uuid), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _lastHeardMicrostamp(usecTimestampNow()), - _publicSocket(publicSocket), - _localSocket(localSocket), - _symmetricSocket(), - _activeSocket(NULL), _connectionSecret(), _bytesReceivedMovingAverage(NULL), _linkedData(NULL), @@ -68,48 +64,6 @@ Node::~Node() { delete _bytesReceivedMovingAverage; } -void Node::setPublicSocket(const HifiSockAddr& publicSocket) { - if (_activeSocket == &_publicSocket) { - // if the active socket was the public socket then reset it to NULL - _activeSocket = NULL; - } - - _publicSocket = publicSocket; -} - -void Node::setLocalSocket(const HifiSockAddr& localSocket) { - if (_activeSocket == &_localSocket) { - // if the active socket was the local socket then reset it to NULL - _activeSocket = NULL; - } - - _localSocket = localSocket; -} - -void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { - if (_activeSocket == &_symmetricSocket) { - // if the active socket was the symmetric socket then reset it to NULL - _activeSocket = NULL; - } - - _symmetricSocket = symmetricSocket; -} - -void Node::activateLocalSocket() { - qDebug() << "Activating local socket for node" << *this; - _activeSocket = &_localSocket; -} - -void Node::activatePublicSocket() { - qDebug() << "Activating public socket for node" << *this; - _activeSocket = &_publicSocket; -} - -void Node::activateSymmetricSocket() { - qDebug() << "Activating symmetric socket for node" << *this; - _activeSocket = &_symmetricSocket; -} - void Node::recordBytesReceived(int bytesReceived) { if (!_bytesReceivedMovingAverage) { _bytesReceivedMovingAverage = new SimpleMovingAverage(100); diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 0d5703b45c..8f276f4e1d 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -21,6 +21,7 @@ #include #include "HifiSockAddr.h" +#include "NetworkPeer.h" #include "NodeData.h" #include "SimpleMovingAverage.h" #include "MovingPercentile.h" @@ -44,7 +45,7 @@ namespace NodeType { const QString& getNodeTypeName(NodeType_t nodeType); } -class Node : public QObject { +class Node : public NetworkPeer { Q_OBJECT public: Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); @@ -64,19 +65,6 @@ public: quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } - - const HifiSockAddr& getPublicSocket() const { return _publicSocket; } - void setPublicSocket(const HifiSockAddr& publicSocket); - const HifiSockAddr& getLocalSocket() const { return _localSocket; } - void setLocalSocket(const HifiSockAddr& localSocket); - const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } - void setSymmetricSocket(const HifiSockAddr& symmetricSocket); - - const HifiSockAddr* getActiveSocket() const { return _activeSocket; } - - void activatePublicSocket(); - void activateLocalSocket(); - void activateSymmetricSocket(); const QUuid& getConnectionSecret() const { return _connectionSecret; } void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } @@ -107,13 +95,9 @@ private: Node& operator=(Node otherNode); NodeType_t _type; - QUuid _uuid; quint64 _wakeTimestamp; quint64 _lastHeardMicrostamp; - HifiSockAddr _publicSocket; - HifiSockAddr _localSocket; - HifiSockAddr _symmetricSocket; - HifiSockAddr* _activeSocket; + QUuid _connectionSecret; SimpleMovingAverage* _bytesReceivedMovingAverage; NodeData* _linkedData; From b2e725c964114bc7af9ef239f1afec707955404d Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 10:18:42 -0700 Subject: [PATCH 029/104] add dashed line support to cube overlay --- interface/src/ui/overlays/Cube3DOverlay.cpp | 35 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 379b74a27f..ad936b74eb 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -57,12 +57,43 @@ void Cube3DOverlay::render() { glPushMatrix(); glm::vec3 positionToCenter = center - position; glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); - glScalef(dimensions.x, dimensions.y, dimensions.z); if (_isSolid) { + glScalef(dimensions.x, dimensions.y, dimensions.z); Application::getInstance()->getDeferredLightingEffect()->renderSolidCube(1.0f); } else { glLineWidth(_lineWidth); - Application::getInstance()->getDeferredLightingEffect()->renderWireCube(1.0f); + + if (getIsDashedLine()) { + glm::vec3 halfDimensions = dimensions / 2.0f; + glm::vec3 bottomLeftNear(-halfDimensions.x, -halfDimensions.y, -halfDimensions.z); + glm::vec3 bottomRightNear(halfDimensions.x, -halfDimensions.y, -halfDimensions.z); + glm::vec3 topLeftNear(-halfDimensions.x, halfDimensions.y, -halfDimensions.z); + glm::vec3 topRightNear(halfDimensions.x, halfDimensions.y, -halfDimensions.z); + + glm::vec3 bottomLeftFar(-halfDimensions.x, -halfDimensions.y, halfDimensions.z); + glm::vec3 bottomRightFar(halfDimensions.x, -halfDimensions.y, halfDimensions.z); + glm::vec3 topLeftFar(-halfDimensions.x, halfDimensions.y, halfDimensions.z); + glm::vec3 topRightFar(halfDimensions.x, halfDimensions.y, halfDimensions.z); + + drawDashedLine(bottomLeftNear, bottomRightNear); + drawDashedLine(bottomRightNear, bottomRightFar); + drawDashedLine(bottomRightFar, bottomLeftFar); + drawDashedLine(bottomLeftFar, bottomLeftNear); + + drawDashedLine(topLeftNear, topRightNear); + drawDashedLine(topRightNear, topRightFar); + drawDashedLine(topRightFar, topLeftFar); + drawDashedLine(topLeftFar, topLeftNear); + + drawDashedLine(bottomLeftNear, topLeftNear); + drawDashedLine(bottomRightNear, topRightNear); + drawDashedLine(bottomLeftFar, topLeftFar); + drawDashedLine(bottomRightFar, topRightFar); + + } else { + glScalef(dimensions.x, dimensions.y, dimensions.z); + Application::getInstance()->getDeferredLightingEffect()->renderWireCube(1.0f); + } } glPopMatrix(); glPopMatrix(); From 5bcbfe96b81448c2aa6bac564efc0afd0f58b392 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 10:19:08 -0700 Subject: [PATCH 030/104] add alpha color support to billboard overlay --- interface/src/ui/overlays/BillboardOverlay.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 8c99b587bc..5c5fea2a14 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -73,7 +73,10 @@ void BillboardOverlay::render() { float x = _fromImage.width() / (2.0f * maxSize); float y = -_fromImage.height() / (2.0f * maxSize); - glColor3f(1.0f, 1.0f, 1.0f); + const float MAX_COLOR = 255; + xColor color = getColor(); + float alpha = getAlpha(); + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); glBegin(GL_QUADS); { glTexCoord2f((float)_fromImage.x() / (float)_size.width(), (float)_fromImage.y() / (float)_size.height()); From d0026cd5293ee3575314295360fd40bf6db8a14f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 10:19:51 -0700 Subject: [PATCH 031/104] latest selection UI features: grabbers, and rotate handles in correct locations --- examples/entitySelectionTool.js | 440 ++++++++++++++++++++++++-------- 1 file changed, 333 insertions(+), 107 deletions(-) diff --git a/examples/entitySelectionTool.js b/examples/entitySelectionTool.js index 917be0416d..1126275e32 100644 --- a/examples/entitySelectionTool.js +++ b/examples/entitySelectionTool.js @@ -13,28 +13,108 @@ SelectionDisplay = (function () { var that = {}; + + var handleHoverColor = { red: 224, green: 67, blue: 36 }; + var handleHoverAlpha = 1.0; + + var rotateHandleColor = { red: 0, green: 0, blue: 0 }; + var rotateHandleAlpha = 0.7; + + + var grabberSizeCorner = 0.025; + var grabberSizeEdge = 0.015; + var grabberSizeFace = 0.025; + var grabberColorCorner = { red: 120, green: 120, blue: 120 }; + var grabberColorEdge = { red: 0, green: 0, blue: 0 }; + var grabberColorFace = { red: 120, green: 120, blue: 120 }; + var grabberLineWidth = 0.5; + var grabberSolid = true; + + var grabberPropertiesCorner = { + position: { x:0, y: 0, z: 0}, + size: grabberSizeCorner, + color: grabberColorCorner, + alpha: 1, + solid: grabberSolid, + visible: false, + dashed: false, + lineWidth: grabberLineWidth, + }; + + var grabberPropertiesEdge = { + position: { x:0, y: 0, z: 0}, + size: grabberSizeEdge, + color: grabberColorEdge, + alpha: 1, + solid: grabberSolid, + visible: false, + dashed: false, + lineWidth: grabberLineWidth, + }; + + var grabberPropertiesFace = { + position: { x:0, y: 0, z: 0}, + size: grabberSizeFace, + color: grabberColorFace, + alpha: 1, + solid: grabberSolid, + visible: false, + dashed: false, + lineWidth: grabberLineWidth, + }; + var selectionBox = Overlays.addOverlay("cube", { position: { x:0, y: 0, z: 0}, size: 1, color: { red: 180, green: 180, blue: 180}, alpha: 1, solid: false, - visible: false + visible: false, + dashed: true, + lineWidth: 1.0, + + /* + pulseMin: 0.0, + pulseMax: 1.0, + pulsePeriod: 2.0, + //glowLevelPulse: 1.0, + //alphaPulse: 0.5, + colorPulse: 1.0 + */ }); + var grabberLBN = Overlays.addOverlay("cube", grabberPropertiesCorner); + var grabberRBN = Overlays.addOverlay("cube", grabberPropertiesCorner); + var grabberLBF = Overlays.addOverlay("cube", grabberPropertiesCorner); + var grabberRBF = Overlays.addOverlay("cube", grabberPropertiesCorner); + var grabberLTN = Overlays.addOverlay("cube", grabberPropertiesCorner); + var grabberRTN = Overlays.addOverlay("cube", grabberPropertiesCorner); + var grabberLTF = Overlays.addOverlay("cube", grabberPropertiesCorner); + var grabberRTF = Overlays.addOverlay("cube", grabberPropertiesCorner); + + var grabberTOP = Overlays.addOverlay("cube", grabberPropertiesFace); + var grabberBOTTOM = Overlays.addOverlay("cube", grabberPropertiesFace); + var grabberLEFT = Overlays.addOverlay("cube", grabberPropertiesFace); + var grabberRIGHT = Overlays.addOverlay("cube", grabberPropertiesFace); + var grabberNEAR = Overlays.addOverlay("cube", grabberPropertiesFace); + var grabberFAR = Overlays.addOverlay("cube", grabberPropertiesFace); + + var grabberEdgeTR = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeTL = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeTF = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeTN = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeBR = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeBL = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeBF = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeBN = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeNR = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeNL = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeFR = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberEdgeFL = Overlays.addOverlay("cube", grabberPropertiesEdge); + + var baseOverlayAngles = { x: 0, y: 0, z: 0 }; var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); - - var baseOfEntityOverlay = Overlays.addOverlay("rectangle3d", { - position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 0}, - alpha: 1, - solid: false, - visible: false, - lineWidth: 2.0, - isDashedLine: true - }); - var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", { position: { x:0, y: 0, z: 0}, size: 1, @@ -45,6 +125,16 @@ SelectionDisplay = (function () { rotation: baseOverlayRotation }); + /* NOTE: not currently in use + var baseOfEntityOverlay = Overlays.addOverlay("rectangle3d", { + position: { x:0, y: 0, z: 0}, + color: { red: 0, green: 0, blue: 0}, + alpha: 1, + solid: false, + visible: false, + lineWidth: 2.0, + isDashedLine: true + }); var heightOfEntityOverlay = Overlays.addOverlay("line3d", { position: { x:0, y: 0, z: 0}, end: { x:0, y: 0, z: 0}, @@ -55,8 +145,7 @@ SelectionDisplay = (function () { lineWidth: 2.0, isDashedLine: true }); - - + */ var yawOverlayAngles = { x: 90, y: 0, z: 0 }; var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); @@ -96,55 +185,76 @@ SelectionDisplay = (function () { rotation: yawOverlayRotation, }); - var yawHandleAngles = { x: 90, y: 90, z: 0 }; - var yawHandleRotation = Quat.fromVec3Degrees(yawHandleAngles); var yawHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 255 }, - alpha: 0.3, + color: rotateHandleColor, + alpha: rotateHandleAlpha, visible: false, size: 0.1, scale: 0.1, - rotation: yawHandleRotation, isFacingAvatar: false }); - var pitchHandleAngles = { x: 90, y: 0, z: 90 }; - var pitchHandleRotation = Quat.fromVec3Degrees(pitchHandleAngles); var pitchHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 255 }, - alpha: 0.3, + color: rotateHandleColor, + alpha: rotateHandleAlpha, visible: false, size: 0.1, scale: 0.1, - rotation: pitchHandleRotation, isFacingAvatar: false }); - var rollHandleAngles = { x: 0, y: 0, z: 180 }; - var rollHandleRotation = Quat.fromVec3Degrees(rollHandleAngles); var rollHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/oocBjCwXpWlHpF9/rotate_arrow_black.png", + url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 255 }, - alpha: 0.3, + color: rotateHandleColor, + alpha: rotateHandleAlpha, visible: false, size: 0.1, scale: 0.1, - rotation: rollHandleRotation, isFacingAvatar: false }); that.cleanup = function () { Overlays.deleteOverlay(selectionBox); - Overlays.deleteOverlay(baseOfEntityOverlay); Overlays.deleteOverlay(baseOfEntityProjectionOverlay); - Overlays.deleteOverlay(heightOfEntityOverlay); + Overlays.deleteOverlay(grabberLBN); + Overlays.deleteOverlay(grabberLBF); + Overlays.deleteOverlay(grabberRBN); + Overlays.deleteOverlay(grabberRBF); + Overlays.deleteOverlay(grabberLTN); + Overlays.deleteOverlay(grabberLTF); + Overlays.deleteOverlay(grabberRTN); + Overlays.deleteOverlay(grabberRTF); + + Overlays.deleteOverlay(grabberTOP); + Overlays.deleteOverlay(grabberBOTTOM); + Overlays.deleteOverlay(grabberLEFT); + Overlays.deleteOverlay(grabberRIGHT); + Overlays.deleteOverlay(grabberNEAR); + Overlays.deleteOverlay(grabberFAR); + + Overlays.deleteOverlay(grabberEdgeTR); + Overlays.deleteOverlay(grabberEdgeTL); + Overlays.deleteOverlay(grabberEdgeTF); + Overlays.deleteOverlay(grabberEdgeTN); + Overlays.deleteOverlay(grabberEdgeBR); + Overlays.deleteOverlay(grabberEdgeBL); + Overlays.deleteOverlay(grabberEdgeBF); + Overlays.deleteOverlay(grabberEdgeBN); + Overlays.deleteOverlay(grabberEdgeNR); + Overlays.deleteOverlay(grabberEdgeNL); + Overlays.deleteOverlay(grabberEdgeFR); + Overlays.deleteOverlay(grabberEdgeFL); + + + //Overlays.deleteOverlay(baseOfEntityOverlay); + //Overlays.deleteOverlay(heightOfEntityOverlay); Overlays.deleteOverlay(yawHandle); Overlays.deleteOverlay(pitchHandle); @@ -170,37 +280,161 @@ SelectionDisplay = (function () { } else { outerAlpha = 0.5; } + + var rotateHandleOffset = 0.05; + + var left = properties.position.x - halfDimensions.x; + var right = properties.position.x + halfDimensions.x; + var bottom = properties.position.y - halfDimensions.y; + var top = properties.position.y + halfDimensions.y; + var near = properties.position.z - halfDimensions.z; + var far = properties.position.z + halfDimensions.z; + var center = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; + + var BLN = { x: left, y: bottom, z: near }; + var BRN = { x: right, y: bottom, z: near }; + var BLF = { x: left, y: bottom, z: far }; + var BRF = { x: right, y: bottom, z: far }; + var TLN = { x: left, y: top, z: near }; + var TRN = { x: right, y: top, z: near }; + var TLF = { x: left, y: top, z: far }; + var TRF = { x: right, y: top, z: far }; + + var yawCorner; + var pitchCorner; + var rollCorner; + + var yawHandleRotation; + var pitchHandleRotation; + var rollHandleRotation; + + // determine which bottom corner we are closest to + /*------------------------------ + example: + + BRF +--------+ BLF + | | + | | + BRN +--------+ BLN + + * + + ------------------------------*/ + + if (MyAvatar.position.x > center.x) { + // must be BRF or BRN + if (MyAvatar.position.z < center.z) { + yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 180 }); + + yawCorner = { x: right + rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: near - rotateHandleOffset }; + + pitchCorner = { x: right + rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset }; + + rollCorner = { x: left - rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset}; + + } else { + yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 270, z: 0 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }); + + yawCorner = { x: right + rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: far + rotateHandleOffset }; + + pitchCorner = { x: left - rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset }; + + rollCorner = { x: right + rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset}; + + } + } else { + // must be BLF or BLN + if (MyAvatar.position.z < center.z) { + yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 90 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); + + yawCorner = { x: left - rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: near - rotateHandleOffset }; + + pitchCorner = { x: right + rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset }; + + rollCorner = { x: left - rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset}; + + + } else { + yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 180, z: 0 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); + + yawCorner = { x: left - rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: far + rotateHandleOffset }; + + pitchCorner = { x: left - rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset }; + + rollCorner = { x: right + rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset}; + } + } Overlays.editOverlay(selectionBox, { - visible: false, - solid:false, - lineWidth: 2.0, - position: { x: properties.position.x, - y: properties.position.y, - z: properties.position.z }, - + visible: true, + position: center, dimensions: properties.dimensions, rotation: properties.rotation, - - pulseMin: 0.1, - pulseMax: 1.0, - pulsePeriod: 4.0, - glowLevelPulse: 1.0, - alphaPulse: 0.5, - colorPulse: -0.5 }); - Overlays.editOverlay(baseOfEntityOverlay, - { - visible: true, - position: { x: properties.position.x, - y: properties.position.y - halfDimensions.y, - z: properties.position.z }, + Overlays.editOverlay(grabberLBN, { visible: true, position: { x: left, y: bottom, z: near } }); + Overlays.editOverlay(grabberRBN, { visible: true, position: { x: right, y: bottom, z: near } }); + Overlays.editOverlay(grabberLBF, { visible: true, position: { x: left, y: bottom, z: far } }); + Overlays.editOverlay(grabberRBF, { visible: true, position: { x: right, y: bottom, z: far } }); + Overlays.editOverlay(grabberLTN, { visible: true, position: { x: left, y: top, z: near } }); + Overlays.editOverlay(grabberRTN, { visible: true, position: { x: right, y: top, z: near } }); + Overlays.editOverlay(grabberLTF, { visible: true, position: { x: left, y: top, z: far } }); + Overlays.editOverlay(grabberRTF, { visible: true, position: { x: right, y: top, z: far } }); + + + Overlays.editOverlay(grabberTOP, { visible: true, position: { x: center.x, y: top, z: center.z } }); + Overlays.editOverlay(grabberBOTTOM, { visible: true, position: { x: center.x, y: bottom, z: center.z } }); + Overlays.editOverlay(grabberLEFT, { visible: true, position: { x: left, y: center.y, z: center.z } }); + Overlays.editOverlay(grabberRIGHT, { visible: true, position: { x: right, y: center.y, z: center.z } }); + Overlays.editOverlay(grabberNEAR, { visible: true, position: { x: center.x, y: center.y, z: near } }); + Overlays.editOverlay(grabberFAR, { visible: true, position: { x: center.x, y: center.y, z: far } }); + + Overlays.editOverlay(grabberEdgeTR, { visible: true, position: { x: right, y: top, z: center.z } }); + Overlays.editOverlay(grabberEdgeTL, { visible: true, position: { x: left, y: top, z: center.z } }); + Overlays.editOverlay(grabberEdgeTF, { visible: true, position: { x: center.x, y: top, z: far } }); + Overlays.editOverlay(grabberEdgeTN, { visible: true, position: { x: center.x, y: top, z: near } }); + Overlays.editOverlay(grabberEdgeBR, { visible: true, position: { x: right, y: bottom, z: center.z } }); + Overlays.editOverlay(grabberEdgeBL, { visible: true, position: { x: left, y: bottom, z: center.z } }); + Overlays.editOverlay(grabberEdgeBF, { visible: true, position: { x: center.x, y: bottom, z: far } }); + Overlays.editOverlay(grabberEdgeBN, { visible: true, position: { x: center.x, y: bottom, z: near } }); + Overlays.editOverlay(grabberEdgeNR, { visible: true, position: { x: right, y: center.y, z: near } }); + Overlays.editOverlay(grabberEdgeNL, { visible: true, position: { x: left, y: center.y, z: near } }); + Overlays.editOverlay(grabberEdgeFR, { visible: true, position: { x: right, y: center.y, z: far } }); + Overlays.editOverlay(grabberEdgeFL, { visible: true, position: { x: left, y: center.y, z: far } }); - dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, - rotation: properties.rotation, - }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { @@ -215,24 +449,10 @@ SelectionDisplay = (function () { rotation: properties.rotation, }); - Overlays.editOverlay(heightOfEntityOverlay, - { - visible: true, - position: { x: properties.position.x - halfDimensions.x, - y: properties.position.y - halfDimensions.y, - z: properties.position.z - halfDimensions.z}, - - end: { x: properties.position.x - halfDimensions.x, - y: properties.position.y + halfDimensions.y, - z: properties.position.z - halfDimensions.z}, - - rotation: properties.rotation, - }); - Overlays.editOverlay(rotateOverlayInner, { - visible: true, + visible: false, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), z: properties.position.z}, @@ -244,67 +464,73 @@ SelectionDisplay = (function () { Overlays.editOverlay(rotateOverlayOuter, { - visible: true, + visible: false, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), z: properties.position.z}, size: outerRadius, innerRadius: 0.9, - startAt: 90, - endAt: 405, + startAt: 0, + endAt: 360, alpha: outerAlpha }); Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, + visible: false, position: { x: properties.position.x, y: properties.position.y - (properties.dimensions.y / 2), z: properties.position.z}, size: outerRadius, - startAt: 45, - endAt: 90, + startAt: 0, + endAt: 0, innerRadius: 0.9 }); - Overlays.editOverlay(yawHandle, - { - visible: true, - position: { x: properties.position.x - (properties.dimensions.x / 2), - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z - (properties.dimensions.z / 2)}, - - //dimensions: properties.dimensions, - //rotation: properties.rotation - }); - - Overlays.editOverlay(pitchHandle, - { - visible: true, - position: { x: properties.position.x + (properties.dimensions.x / 2), - y: properties.position.y + (properties.dimensions.y / 2), - z: properties.position.z - (properties.dimensions.z / 2)}, - }); - - Overlays.editOverlay(rollHandle, - { - visible: true, - position: { x: properties.position.x - (properties.dimensions.x / 2), - y: properties.position.y + (properties.dimensions.y / 2), - z: properties.position.z + (properties.dimensions.z / 2)}, - }); - + Overlays.editOverlay(yawHandle, { visible: true, position: yawCorner, rotation: yawHandleRotation}); + Overlays.editOverlay(pitchHandle, { visible: true, position: pitchCorner, rotation: pitchHandleRotation}); + Overlays.editOverlay(rollHandle, { visible: true, position: rollCorner, rotation: rollHandleRotation}); Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; that.hideSelection = function (entityID) { Overlays.editOverlay(selectionBox, { visible: false }); - Overlays.editOverlay(baseOfEntityOverlay, { visible: false }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: false }); - Overlays.editOverlay(heightOfEntityOverlay, { visible: false }); + Overlays.editOverlay(grabberLBN, { visible: false }); + Overlays.editOverlay(grabberLBF, { visible: false }); + Overlays.editOverlay(grabberRBN, { visible: false }); + Overlays.editOverlay(grabberRBF, { visible: false }); + Overlays.editOverlay(grabberLTN, { visible: false }); + Overlays.editOverlay(grabberLTF, { visible: false }); + Overlays.editOverlay(grabberRTN, { visible: false }); + Overlays.editOverlay(grabberRTF, { visible: false }); + + Overlays.editOverlay(grabberTOP, { visible: false }); + Overlays.editOverlay(grabberBOTTOM, { visible: false }); + Overlays.editOverlay(grabberLEFT, { visible: false }); + Overlays.editOverlay(grabberRIGHT, { visible: false }); + Overlays.editOverlay(grabberNEAR, { visible: false }); + Overlays.editOverlay(grabberFAR, { visible: false }); + + Overlays.editOverlay(grabberEdgeTR, { visible: false }); + Overlays.editOverlay(grabberEdgeTL, { visible: false }); + Overlays.editOverlay(grabberEdgeTF, { visible: false }); + Overlays.editOverlay(grabberEdgeTN, { visible: false }); + Overlays.editOverlay(grabberEdgeBR, { visible: false }); + Overlays.editOverlay(grabberEdgeBL, { visible: false }); + Overlays.editOverlay(grabberEdgeBF, { visible: false }); + Overlays.editOverlay(grabberEdgeBN, { visible: false }); + Overlays.editOverlay(grabberEdgeNR, { visible: false }); + Overlays.editOverlay(grabberEdgeNL, { visible: false }); + Overlays.editOverlay(grabberEdgeFR, { visible: false }); + Overlays.editOverlay(grabberEdgeFL, { visible: false }); + + // Not currently in use + //Overlays.editOverlay(baseOfEntityOverlay, { visible: false }); + //Overlays.editOverlay(heightOfEntityOverlay, { visible: false }); Overlays.editOverlay(yawHandle, { visible: false }); Overlays.editOverlay(pitchHandle, { visible: false }); From d3761cd7537170f719e4ea3930fe8f39aea7925b Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 2 Oct 2014 12:25:19 -0700 Subject: [PATCH 032/104] Cuboid shape implementation. --- libraries/metavoxels/src/MetavoxelData.cpp | 56 +++++++++++++++++++--- libraries/metavoxels/src/MetavoxelData.h | 7 ++- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index b71baa2c27..56b3cfe1c2 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -2247,17 +2247,50 @@ AttributeValue Sphere::getNormal(MetavoxelInfo& info, int alpha) const { } Cuboid::Cuboid() { - connect(this, &Cuboid::translationChanged, this, &Cuboid::updateBounds); - connect(this, &Cuboid::rotationChanged, this, &Cuboid::updateBounds); - connect(this, &Cuboid::scaleChanged, this, &Cuboid::updateBounds); - updateBounds(); + connect(this, &Cuboid::translationChanged, this, &Cuboid::updateBoundsAndPlanes); + connect(this, &Cuboid::rotationChanged, this, &Cuboid::updateBoundsAndPlanes); + connect(this, &Cuboid::scaleChanged, this, &Cuboid::updateBoundsAndPlanes); + updateBoundsAndPlanes(); } bool Cuboid::contains(const glm::vec3& point) { - return false; + glm::vec4 point4(point, 1.0f); + for (int i = 0; i < PLANE_COUNT; i++) { + if (glm::dot(_planes[i], point4) > 0.0f) { + return false; + } + } + return true; } bool Cuboid::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { + glm::vec4 start4(start, 1.0f); + glm::vec4 vector = glm::vec4(end - start, 0.0f); + for (int i = 0; i < PLANE_COUNT; i++) { + // first check the segment against the plane + float divisor = glm::dot(_planes[i], vector); + if (glm::abs(divisor) < EPSILON) { + continue; + } + float t = -glm::dot(_planes[i], start4) / divisor; + if (t < 0.0f || t > 1.0f) { + continue; + } + // now that we've established that it intersects the plane, check against the other sides + glm::vec4 point = start4 + vector * t; + const int PLANES_PER_AXIS = 2; + int indexOffset = ((i / PLANES_PER_AXIS) + 1) * PLANES_PER_AXIS; + for (int j = 0; j < PLANE_COUNT - PLANES_PER_AXIS; j++) { + if (glm::dot(_planes[(indexOffset + j) % PLANE_COUNT], point) > 0.0f) { + goto outerContinue; + } + } + distance = t; + normal = glm::vec3(_planes[i]); + return true; + + outerContinue: ; + } return false; } @@ -2265,9 +2298,18 @@ QByteArray Cuboid::getRendererClassName() const { return "CuboidRenderer"; } -void Cuboid::updateBounds() { +void Cuboid::updateBoundsAndPlanes() { glm::vec3 extent(getScale(), getScale(), getScale()); - setBounds(glm::translate(getTranslation()) * glm::mat4_cast(getRotation()) * Box(-extent, extent)); + glm::mat4 rotationMatrix = glm::mat4_cast(getRotation()); + setBounds(glm::translate(getTranslation()) * rotationMatrix * Box(-extent, extent)); + + glm::vec4 translation4 = glm::vec4(getTranslation(), 1.0f); + _planes[0] = glm::vec4(glm::vec3(rotationMatrix[0]), -glm::dot(rotationMatrix[0], translation4) - getScale()); + _planes[1] = glm::vec4(glm::vec3(-rotationMatrix[0]), glm::dot(rotationMatrix[0], translation4) - getScale()); + _planes[2] = glm::vec4(glm::vec3(rotationMatrix[1]), -glm::dot(rotationMatrix[1], translation4) - getScale()); + _planes[3] = glm::vec4(glm::vec3(-rotationMatrix[1]), glm::dot(rotationMatrix[1], translation4) - getScale()); + _planes[4] = glm::vec4(glm::vec3(rotationMatrix[2]), -glm::dot(rotationMatrix[2], translation4) - getScale()); + _planes[5] = glm::vec4(glm::vec3(-rotationMatrix[2]), glm::dot(rotationMatrix[2], translation4) - getScale()); } StaticModel::StaticModel() { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 92aeeaa247..48f39e57e6 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -791,7 +791,12 @@ protected: private slots: - void updateBounds(); + void updateBoundsAndPlanes(); + +private: + + static const int PLANE_COUNT = 6; + glm::vec4 _planes[PLANE_COUNT]; }; /// A static 3D model loaded from the network. From bce0147776c6b5f19252e5f7e5ff48646bce3409 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 2 Oct 2014 12:51:56 -0700 Subject: [PATCH 033/104] Added aspect fields to cuboids. --- interface/src/MetavoxelSystem.cpp | 1 + libraries/metavoxels/src/MetavoxelData.cpp | 29 +++++++++++++++++----- libraries/metavoxels/src/MetavoxelData.h | 20 +++++++++++++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 64307593f0..fe4d56c6fd 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -2357,6 +2357,7 @@ void CuboidRenderer::renderUnclipped(const glm::vec4& color, Mode mode) { glm::quat rotation = cuboid->getRotation(); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glScalef(1.0f, cuboid->getAspectY(), cuboid->getAspectZ()); glutSolidCube(cuboid->getScale() * 2.0f); diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 56b3cfe1c2..07c55bd0ab 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -2246,13 +2246,30 @@ AttributeValue Sphere::getNormal(MetavoxelInfo& info, int alpha) const { return AttributeValue(getAttributes().at(1), encodeInline(color)); } -Cuboid::Cuboid() { +Cuboid::Cuboid() : + _aspectY(1.0f), + _aspectZ(1.0f) { + connect(this, &Cuboid::translationChanged, this, &Cuboid::updateBoundsAndPlanes); connect(this, &Cuboid::rotationChanged, this, &Cuboid::updateBoundsAndPlanes); connect(this, &Cuboid::scaleChanged, this, &Cuboid::updateBoundsAndPlanes); + connect(this, &Cuboid::aspectYChanged, this, &Cuboid::updateBoundsAndPlanes); + connect(this, &Cuboid::aspectZChanged, this, &Cuboid::updateBoundsAndPlanes); updateBoundsAndPlanes(); } +void Cuboid::setAspectY(float aspectY) { + if (_aspectY != aspectY) { + emit aspectYChanged(_aspectY = aspectY); + } +} + +void Cuboid::setAspectZ(float aspectZ) { + if (_aspectZ != aspectZ) { + emit aspectZChanged(_aspectZ = aspectZ); + } +} + bool Cuboid::contains(const glm::vec3& point) { glm::vec4 point4(point, 1.0f); for (int i = 0; i < PLANE_COUNT; i++) { @@ -2299,17 +2316,17 @@ QByteArray Cuboid::getRendererClassName() const { } void Cuboid::updateBoundsAndPlanes() { - glm::vec3 extent(getScale(), getScale(), getScale()); + glm::vec3 extent(getScale(), getScale() * _aspectY, getScale() * _aspectZ); glm::mat4 rotationMatrix = glm::mat4_cast(getRotation()); setBounds(glm::translate(getTranslation()) * rotationMatrix * Box(-extent, extent)); glm::vec4 translation4 = glm::vec4(getTranslation(), 1.0f); _planes[0] = glm::vec4(glm::vec3(rotationMatrix[0]), -glm::dot(rotationMatrix[0], translation4) - getScale()); _planes[1] = glm::vec4(glm::vec3(-rotationMatrix[0]), glm::dot(rotationMatrix[0], translation4) - getScale()); - _planes[2] = glm::vec4(glm::vec3(rotationMatrix[1]), -glm::dot(rotationMatrix[1], translation4) - getScale()); - _planes[3] = glm::vec4(glm::vec3(-rotationMatrix[1]), glm::dot(rotationMatrix[1], translation4) - getScale()); - _planes[4] = glm::vec4(glm::vec3(rotationMatrix[2]), -glm::dot(rotationMatrix[2], translation4) - getScale()); - _planes[5] = glm::vec4(glm::vec3(-rotationMatrix[2]), glm::dot(rotationMatrix[2], translation4) - getScale()); + _planes[2] = glm::vec4(glm::vec3(rotationMatrix[1]), -glm::dot(rotationMatrix[1], translation4) - getScale() * _aspectY); + _planes[3] = glm::vec4(glm::vec3(-rotationMatrix[1]), glm::dot(rotationMatrix[1], translation4) - getScale() * _aspectY); + _planes[4] = glm::vec4(glm::vec3(rotationMatrix[2]), -glm::dot(rotationMatrix[2], translation4) - getScale() * _aspectZ); + _planes[5] = glm::vec4(glm::vec3(-rotationMatrix[2]), glm::dot(rotationMatrix[2], translation4) - getScale() * _aspectZ); } StaticModel::StaticModel() { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 48f39e57e6..c693cf723c 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -777,14 +777,27 @@ private: /// A cuboid. class Cuboid : public ColorTransformable { Q_OBJECT - + Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged) + Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged) + public: Q_INVOKABLE Cuboid(); + void setAspectY(float aspectY); + float getAspectY() const { return _aspectY; } + + void setAspectZ(float aspectZ); + float getAspectZ() const { return _aspectZ; } + virtual bool contains(const glm::vec3& point); virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); - + +signals: + + void aspectYChanged(float aspectY); + void aspectZChanged(float aspectZ); + protected: virtual QByteArray getRendererClassName() const; @@ -794,6 +807,9 @@ private slots: void updateBoundsAndPlanes(); private: + + float _aspectY; + float _aspectZ; static const int PLANE_COUNT = 6; glm::vec4 _planes[PLANE_COUNT]; From 4e19cc21f6cba9db1b0f0586402ef409985d7b58 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 2 Oct 2014 13:01:39 -0700 Subject: [PATCH 034/104] Remove the "box edit" in favor of using the spanner edit. --- interface/src/ui/MetavoxelEditor.cpp | 10 +- .../metavoxels/src/MetavoxelMessages.cpp | 238 ------------------ libraries/metavoxels/src/MetavoxelMessages.h | 19 -- 3 files changed, 8 insertions(+), 259 deletions(-) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index c9f407db09..dffc02ee07 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1393,8 +1393,14 @@ void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& } QColor color = _color->getColor(); color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialBoxEdit(Box(minimum, maximum), - _editor->getGridSpacing(), material, color)) }; + + Cuboid* cuboid = new Cuboid(); + cuboid->setTranslation((maximum + minimum) * 0.5f); + glm::vec3 vector = (maximum - minimum) * 0.5f; + cuboid->setScale(vector.x); + cuboid->setAspectY(vector.y / vector.x); + cuboid->setAspectZ(vector.z / vector.x); + MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(cuboid), material, color)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 7fb605d066..1a18079257 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -611,244 +611,6 @@ const int VOXEL_BLOCK_SAMPLES = VOXEL_BLOCK_SIZE + 1; const int VOXEL_BLOCK_AREA = VOXEL_BLOCK_SAMPLES * VOXEL_BLOCK_SAMPLES; const int VOXEL_BLOCK_VOLUME = VOXEL_BLOCK_AREA * VOXEL_BLOCK_SAMPLES; -class VoxelMaterialBoxEditVisitor : public MetavoxelVisitor { -public: - - VoxelMaterialBoxEditVisitor(const Box& region, float granularity, - const SharedObjectPointer& material, const QColor& color); - - virtual int visit(MetavoxelInfo& info); - -private: - - Box _region; - SharedObjectPointer _material; - QColor _color; - float _blockSize; -}; - -VoxelMaterialBoxEditVisitor::VoxelMaterialBoxEditVisitor(const Box& region, float granularity, - const SharedObjectPointer& material, const QColor& color) : - MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << - AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << - AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector() << - AttributeRegistry::getInstance()->getVoxelColorAttribute() << - AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << - AttributeRegistry::getInstance()->getVoxelMaterialAttribute()), - _region(region), - _material(material), - _color(color), - _blockSize(granularity * VOXEL_BLOCK_SIZE) { -} - -int VoxelMaterialBoxEditVisitor::visit(MetavoxelInfo& info) { - Box bounds = info.getBounds(); - if (!bounds.intersects(_region)) { - return STOP_RECURSION; - } - if (info.size > _blockSize) { - return DEFAULT_ORDER; - } - VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue(); - QVector oldColorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ? - colorPointer->getContents() : QVector(VOXEL_BLOCK_VOLUME); - QVector colorContents = oldColorContents; - - Box overlap = info.getBounds().getIntersection(_region); - float scale = VOXEL_BLOCK_SIZE / info.size; - overlap.minimum = (overlap.minimum - info.minimum) * scale; - overlap.maximum = (overlap.maximum - info.minimum) * scale; - int minX = glm::ceil(overlap.minimum.x); - int minY = glm::ceil(overlap.minimum.y); - int minZ = glm::ceil(overlap.minimum.z); - int sizeX = (int)overlap.maximum.x - minX + 1; - int sizeY = (int)overlap.maximum.y - minY + 1; - int sizeZ = (int)overlap.maximum.z - minZ + 1; - - QRgb rgb = _color.rgba(); - for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA) { - for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; destY += VOXEL_BLOCK_SAMPLES) { - for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++) { - *destX = rgb; - } - } - } - - VoxelColorDataPointer newColorPointer(new VoxelColorData(colorContents, VOXEL_BLOCK_SAMPLES)); - info.outputValues[0] = AttributeValue(info.inputValues.at(0).getAttribute(), - encodeInline(newColorPointer)); - - VoxelHermiteDataPointer hermitePointer = info.inputValues.at(1).getInlineValue(); - QVector hermiteContents = (hermitePointer && hermitePointer->getSize() == VOXEL_BLOCK_SAMPLES) ? - hermitePointer->getContents() : QVector(VOXEL_BLOCK_VOLUME * VoxelHermiteData::EDGE_COUNT); - int hermiteArea = VOXEL_BLOCK_AREA * VoxelHermiteData::EDGE_COUNT; - int hermiteSamples = VOXEL_BLOCK_SAMPLES * VoxelHermiteData::EDGE_COUNT; - - int hermiteMinX = minX, hermiteMinY = minY, hermiteMinZ = minZ; - int hermiteSizeX = sizeX, hermiteSizeY = sizeY, hermiteSizeZ = sizeZ; - if (minX > 0) { - hermiteMinX--; - hermiteSizeX++; - } - if (minY > 0) { - hermiteMinY--; - hermiteSizeY++; - } - if (minZ > 0) { - hermiteMinZ--; - hermiteSizeZ++; - } - const int NORMAL_MAX = 127; - QRgb* hermiteDestZ = hermiteContents.data() + hermiteMinZ * hermiteArea + hermiteMinY * hermiteSamples + - hermiteMinX * VoxelHermiteData::EDGE_COUNT; - for (int z = hermiteMinZ, hermiteMaxZ = z + hermiteSizeZ - 1; z <= hermiteMaxZ; z++, hermiteDestZ += hermiteArea) { - QRgb* hermiteDestY = hermiteDestZ; - for (int y = hermiteMinY, hermiteMaxY = y + hermiteSizeY - 1; y <= hermiteMaxY; y++, hermiteDestY += hermiteSamples) { - QRgb* hermiteDestX = hermiteDestY; - for (int x = hermiteMinX, hermiteMaxX = x + hermiteSizeX - 1; x <= hermiteMaxX; x++, - hermiteDestX += VoxelHermiteData::EDGE_COUNT) { - // internal edges are set to zero; border edges (when non-terminal) are set to the intersection values - if ((x == hermiteMinX || x == hermiteMaxX) && x != VOXEL_BLOCK_SIZE) { - int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; - const QRgb* color = colorContents.constData() + offset; - int alpha0 = qAlpha(color[0]); - int alpha1 = qAlpha(color[1]); - if (alpha0 != alpha1) { - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) { - if (x == hermiteMinX) { - int alpha = (overlap.minimum.x - x) * EIGHT_BIT_MAXIMUM; - if (alpha <= qAlpha(hermiteDestX[0])) { - hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, alpha); - } - } else { - int alpha = (overlap.maximum.x - x) * EIGHT_BIT_MAXIMUM; - if (alpha >= qAlpha(hermiteDestX[0])) { - hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, alpha); - } - } - } else { - hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, - ((x == hermiteMinX ? overlap.minimum.x : overlap.maximum.x) - x) * EIGHT_BIT_MAXIMUM); - } - } else { - hermiteDestX[0] = 0x0; - } - } else { - hermiteDestX[0] = 0x0; - } - if ((y == hermiteMinY || y == hermiteMaxY) && y != VOXEL_BLOCK_SIZE) { - int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; - const QRgb* color = colorContents.constData() + offset; - int alpha0 = qAlpha(color[0]); - int alpha2 = qAlpha(color[VOXEL_BLOCK_SAMPLES]); - if (alpha0 != alpha2) { - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) { - if (y == hermiteMinY) { - int alpha = (overlap.minimum.y - y) * EIGHT_BIT_MAXIMUM; - if (alpha <= qAlpha(hermiteDestX[1])) { - hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, alpha); - } - } else { - int alpha = (overlap.maximum.y - y) * EIGHT_BIT_MAXIMUM; - if (alpha >= qAlpha(hermiteDestX[1])) { - hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, alpha); - } - } - } else { - hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, - ((y == hermiteMinY ? overlap.minimum.y : overlap.maximum.y) - y) * EIGHT_BIT_MAXIMUM); - } - } else { - hermiteDestX[1] = 0x0; - } - } else { - hermiteDestX[1] = 0x0; - } - if ((z == hermiteMinZ || z == hermiteMaxZ) && z != VOXEL_BLOCK_SIZE) { - int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; - const QRgb* color = colorContents.constData() + offset; - int alpha0 = qAlpha(color[0]); - int alpha4 = qAlpha(color[VOXEL_BLOCK_AREA]); - if (alpha0 != alpha4) { - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) { - if (z == hermiteMinZ) { - int alpha = (overlap.minimum.z - z) * EIGHT_BIT_MAXIMUM; - if (alpha <= qAlpha(hermiteDestX[2])) { - hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, alpha); - } - } else { - int alpha = (overlap.maximum.z - z) * EIGHT_BIT_MAXIMUM; - if (alpha >= qAlpha(hermiteDestX[2])) { - hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, alpha); - } - } - } else { - hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, - ((z == hermiteMinZ ? overlap.minimum.z : overlap.maximum.z) - z) * EIGHT_BIT_MAXIMUM); - } - } else { - hermiteDestX[2] = 0x0; - } - } else { - hermiteDestX[2] = 0x0; - } - } - } - } - - VoxelHermiteDataPointer newHermitePointer(new VoxelHermiteData(hermiteContents, VOXEL_BLOCK_SAMPLES)); - info.outputValues[1] = AttributeValue(info.inputValues.at(1).getAttribute(), - encodeInline(newHermitePointer)); - - VoxelMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue(); - QByteArray materialContents; - QVector materials; - if (materialPointer && materialPointer->getSize() == VOXEL_BLOCK_SAMPLES) { - materialContents = materialPointer->getContents(); - materials = materialPointer->getMaterials(); - - } else { - materialContents = QByteArray(VOXEL_BLOCK_VOLUME, 0); - } - - uchar materialIndex = getMaterialIndex(_material, materials, materialContents); - for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA) { - for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; destY += VOXEL_BLOCK_SAMPLES) { - for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++) { - *destX = materialIndex; - } - } - } - - clearUnusedMaterials(materials, materialContents); - VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, VOXEL_BLOCK_SAMPLES, materials)); - info.outputValues[2] = AttributeValue(_inputs.at(2), encodeInline(newMaterialPointer)); - - return STOP_RECURSION; -} - -VoxelMaterialBoxEdit::VoxelMaterialBoxEdit(const Box& region, float granularity, - const SharedObjectPointer& material, const QColor& averageColor) : - region(region), - granularity(granularity), - material(material), - averageColor(averageColor) { -} - -void VoxelMaterialBoxEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - // expand to fit the entire edit - while (!data.getBounds().contains(region)) { - data.expand(); - } - VoxelMaterialBoxEditVisitor visitor(region, granularity, material, averageColor); - data.guide(visitor); -} - VoxelMaterialSpannerEdit::VoxelMaterialSpannerEdit(const SharedObjectPointer& spanner, const SharedObjectPointer& material, const QColor& averageColor) : spanner(spanner), diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index c94eaf511d..bf59a1e691 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -243,25 +243,6 @@ public: DECLARE_STREAMABLE_METATYPE(PaintHeightfieldMaterialEdit) -/// An edit that sets the materials of voxels within a box to a value. -class VoxelMaterialBoxEdit : public MetavoxelEdit { - STREAMABLE - -public: - - STREAM Box region; - STREAM float granularity; - STREAM SharedObjectPointer material; - STREAM QColor averageColor; - - VoxelMaterialBoxEdit(const Box& region = Box(), float granularity = 0.0f, - const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor()); - - virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; -}; - -DECLARE_STREAMABLE_METATYPE(VoxelMaterialBoxEdit) - /// An edit that sets the materials of voxels within a spanner to a value. class VoxelMaterialSpannerEdit : public MetavoxelEdit { STREAMABLE From cca1c30207d5502a0806e24bdc99b0eab65f3b1b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 13:39:58 -0700 Subject: [PATCH 035/104] build out more of the ice-server, make connect requests from NodeList --- ice-server/src/IceServer.cpp | 54 +++++++++- ice-server/src/IceServer.h | 11 +- libraries/networking/src/DomainHandler.cpp | 2 + libraries/networking/src/DomainHandler.h | 3 + libraries/networking/src/NetworkPeer.cpp | 7 ++ libraries/networking/src/NetworkPeer.h | 9 ++ libraries/networking/src/Node.h | 3 - libraries/networking/src/NodeList.cpp | 115 ++++++++++++--------- libraries/networking/src/NodeList.h | 2 + libraries/networking/src/PacketHeaders.h | 3 +- 10 files changed, 154 insertions(+), 55 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 6543c41c31..cea05ebe30 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -9,10 +9,62 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "IceServer.h" IceServer::IceServer(int argc, char* argv[]) : - QCoreApplication(argc, argv) + QCoreApplication(argc, argv), + _serverSocket() { + // start the ice-server socket + qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT; + _serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT); + // call our process datagrams slot when the UDP socket has packets ready + connect(&_serverSocket, &QUdpSocket::readyRead, this, &IceServer::processDatagrams); +} + +void IceServer::processDatagrams() { + HifiSockAddr sendingSockAddr; + QByteArray incomingPacket; + + while (_serverSocket.hasPendingDatagrams()) { + incomingPacket.resize(_serverSocket.pendingDatagramSize()); + + _serverSocket.readDatagram(incomingPacket.data(), incomingPacket.size(), + sendingSockAddr.getAddressPointer(), sendingSockAddr.getPortPointer()); + + + if (packetTypeForPacket(incomingPacket) == PacketTypeIceServerHeartbeat) { + QUuid senderUUID = uuidFromPacketHeader(incomingPacket); + + // pull the public and private sock addrs for this peer + HifiSockAddr publicSocket, localSocket; + + QDataStream hearbeatStream(incomingPacket); + hearbeatStream.skipRawData(numBytesForPacketHeader(incomingPacket)); + + hearbeatStream >> publicSocket >> localSocket; + + // make sure we have this sender in our peer hash + SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); + + if (!matchingPeer) { + // if we don't have this sender we need to create them now + matchingPeer = SharedNetworkPeer(new NetworkPeer(senderUUID, publicSocket, localSocket)); + + qDebug() << "Added a new network peer" << *matchingPeer; + + } else { + // we already had the peer so just potentially update their sockets + matchingPeer->setPublicSocket(publicSocket); + matchingPeer->setLocalSocket(localSocket); + } + + // check if this node also included a UUID that they would like to connect to + QUuid connectRequestUUID; + hearbeatStream >> connectRequestUUID; + } + } } diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 7d56737ca1..3db9a6b2e2 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -12,11 +12,20 @@ #ifndef hifi_IceServer_h #define hifi_IceServer_h -#include +#include +#include +#include + +#include class IceServer : public QCoreApplication { public: IceServer(int argc, char* argv[]); +private slots: + void processDatagrams(); +private: + QHash _activePeers; + QUdpSocket _serverSocket; }; #endif // hifi_IceServer_h \ No newline at end of file diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 9c0ae55d7e..b13f598a08 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -25,6 +25,7 @@ DomainHandler::DomainHandler(QObject* parent) : _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _assignmentUUID(), + _requiresICE(true), _isConnected(false), _handshakeTimer(NULL), _settingsObject(), @@ -35,6 +36,7 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); + _requiresICE = true; _isConnected = false; emit disconnectedFromDomain(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index bfdb5d7f38..9733a8bff3 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -54,6 +54,8 @@ public: const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } + bool requiresICE() const { return _requiresICE; } + bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); @@ -85,6 +87,7 @@ private: QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; + bool _requiresICE; bool _isConnected; QTimer* _handshakeTimer; QJsonObject _settingsObject; diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index bb26b2a119..57a4a97331 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -63,4 +63,11 @@ void NetworkPeer::activatePublicSocket() { void NetworkPeer::activateSymmetricSocket() { qDebug() << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); _activeSocket = &_symmetricSocket; +} + +QDebug operator<<(QDebug debug, const NetworkPeer &peer) { + debug << uuidStringWithoutCurlyBraces(peer.getUUID()) + << "- public:" << peer.getPublicSocket() + << "- local:" << peer.getLocalSocket(); + return debug; } \ No newline at end of file diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index b4e472b80c..ae1812cf87 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -17,10 +17,16 @@ #include "HifiSockAddr.h" +const QString ICE_SERVER_HOSTNAME = "localhost"; +const int ICE_SERVER_DEFAULT_PORT = 7337; + class NetworkPeer : public QObject { public: NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); + const QUuid& getUUID() const { return _uuid; } + void setUUID(const QUuid& uuid) { _uuid = uuid; } + const HifiSockAddr& getPublicSocket() const { return _publicSocket; } void setPublicSocket(const HifiSockAddr& publicSocket); const HifiSockAddr& getLocalSocket() const { return _localSocket; } @@ -43,4 +49,7 @@ protected: HifiSockAddr* _activeSocket; }; +QDebug operator<<(QDebug debug, const NetworkPeer &peer); +typedef QSharedPointer SharedNetworkPeer; + #endif // hifi_NetworkPeer_h \ No newline at end of file diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 8f276f4e1d..f300499add 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -57,9 +57,6 @@ public: char getType() const { return _type; } void setType(char type) { _type = type; } - const QUuid& getUUID() const { return _uuid; } - void setUUID(const QUuid& uuid) { _uuid = uuid; } - quint64 getWakeTimestamp() const { return _wakeTimestamp; } void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 3a1ed79f77..451b30c58b 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -243,61 +243,78 @@ void NodeList::sendDomainServerCheckIn() { // send a STUN request to figure it out sendSTUNRequest(); } else if (!_domainHandler.getIP().isNull()) { - - bool isUsingDTLS = false; - PacketType domainPacketType = !_domainHandler.isConnected() + if (!_domainHandler.isConnected() && _domainHandler.requiresICE()) { + sendICERequestForDomainConnection(); + } else { + bool isUsingDTLS = false; + + PacketType domainPacketType = !_domainHandler.isConnected() ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; - - if (!_domainHandler.isConnected()) { - qDebug() << "Sending connect request to domain-server at" << _domainHandler.getHostname(); + + if (!_domainHandler.isConnected()) { + qDebug() << "Sending connect request to domain-server at" << _domainHandler.getHostname(); + } + + // construct the DS check in packet + QUuid packetUUID = _sessionUUID; + + if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) { + // this is a connect request and we're an assigned node + // so set our packetUUID as the assignment UUID + packetUUID = _domainHandler.getAssignmentUUID(); + } + + QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); + QDataStream packetStream(&domainServerPacket, QIODevice::Append); + + // pack our data to send to the domain-server + packetStream << _ownerType << _publicSockAddr + << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) + << (quint8) _nodeTypesOfInterest.size(); + + // copy over the bytes for node types of interest, if required + foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { + packetStream << nodeTypeOfInterest; + } + + if (!isUsingDTLS) { + writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid()); + } + + const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; + static unsigned int numDomainCheckins = 0; + + // send a STUN request every Nth domain server check in so we update our public socket, if required + if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { + sendSTUNRequest(); + } + + if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS + // so emit our signal that indicates that + emit limitOfSilentDomainCheckInsReached(); + } + + // increment the count of un-replied check-ins + _numNoReplyDomainCheckIns++; } - - // construct the DS check in packet - QUuid packetUUID = _sessionUUID; - - if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) { - // this is a connect request and we're an assigned node - // so set our packetUUID as the assignment UUID - packetUUID = _domainHandler.getAssignmentUUID(); - } - - QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); - QDataStream packetStream(&domainServerPacket, QIODevice::Append); - - // pack our data to send to the domain-server - packetStream << _ownerType << _publicSockAddr - << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) - << (quint8) _nodeTypesOfInterest.size(); - - // copy over the bytes for node types of interest, if required - foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { - packetStream << nodeTypeOfInterest; - } - - if (!isUsingDTLS) { - writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid()); - } - - const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; - static unsigned int numDomainCheckins = 0; - - // send a STUN request every Nth domain server check in so we update our public socket, if required - if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { - sendSTUNRequest(); - } - - if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { - // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS - // so emit our signal that indicates that - emit limitOfSilentDomainCheckInsReached(); - } - - // increment the count of un-replied check-ins - _numNoReplyDomainCheckIns++; } } +void NodeList::sendICERequestForDomainConnection() { + QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat); + QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append); + + iceDataStream << _publicSockAddr << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()); + iceDataStream << _domainHandler.getUUID(); + + qDebug() << "Sending packet to ICE server to request connection info for peer with ID" + << uuidStringWithoutCurlyBraces(_domainHandler.getUUID()); + + _nodeSocket.writeDatagram(iceRequestByteArray, QHostAddress::LocalHost, ICE_SERVER_DEFAULT_PORT); +} + int NodeList::processDomainServerList(const QByteArray& packet) { // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 8293d0a05a..ad0f74e517 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -91,6 +91,8 @@ private: void sendSTUNRequest(); bool processSTUNResponse(const QByteArray& packet); + void sendICERequestForDomainConnection(); + void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 207cf680d3..aaab9c2928 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -70,7 +70,8 @@ enum PacketType { PacketTypeVoxelEditNack, PacketTypeParticleEditNack, PacketTypeEntityEditNack, // 48 - PacketTypeSignedTransactionPayment + PacketTypeSignedTransactionPayment, + PacketTypeIceServerHeartbeat }; typedef char PacketVersion; From 11659401ee9281f3b4f7c5daaf8790740104a51b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 13:47:53 -0700 Subject: [PATCH 036/104] add an identifier for repeated requests to the ice server --- ice-server/src/IceServer.cpp | 4 +++- libraries/networking/src/NodeList.cpp | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index cea05ebe30..0877c8591f 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -53,13 +53,15 @@ void IceServer::processDatagrams() { if (!matchingPeer) { // if we don't have this sender we need to create them now matchingPeer = SharedNetworkPeer(new NetworkPeer(senderUUID, publicSocket, localSocket)); + _activePeers.insert(senderUUID, matchingPeer); qDebug() << "Added a new network peer" << *matchingPeer; - } else { // we already had the peer so just potentially update their sockets matchingPeer->setPublicSocket(publicSocket); matchingPeer->setLocalSocket(localSocket); + + qDebug() << "Matched hearbeat to existing network peer" << *matchingPeer; } // check if this node also included a UUID that they would like to connect to diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 451b30c58b..afefd2a5c1 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -250,7 +250,7 @@ void NodeList::sendDomainServerCheckIn() { bool isUsingDTLS = false; PacketType domainPacketType = !_domainHandler.isConnected() - ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; + ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; if (!_domainHandler.isConnected()) { qDebug() << "Sending connect request to domain-server at" << _domainHandler.getHostname(); @@ -303,7 +303,10 @@ void NodeList::sendDomainServerCheckIn() { } void NodeList::sendICERequestForDomainConnection() { - QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat); + + static QUuid iceUUID = QUuid::createUuid(); + + QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat, iceUUID); QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append); iceDataStream << _publicSockAddr << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()); From 262054d0ebbe30667bf6655d20322e7b2500b9bc Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 14:18:17 -0700 Subject: [PATCH 037/104] handle ICE requirement in address manager response --- ice-server/src/IceServer.cpp | 4 + interface/src/Application.cpp | 7 +- libraries/networking/src/AddressManager.cpp | 20 +++- libraries/networking/src/AddressManager.h | 3 +- libraries/networking/src/DomainHandler.cpp | 17 +++- libraries/networking/src/DomainHandler.h | 5 +- libraries/networking/src/NodeList.cpp | 106 ++++++++++---------- 7 files changed, 97 insertions(+), 65 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 0877c8591f..a24abd3a49 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -67,6 +67,10 @@ void IceServer::processDatagrams() { // check if this node also included a UUID that they would like to connect to QUuid connectRequestUUID; hearbeatStream >> connectRequestUUID; + + if (!connectRequestUUID.isNull()) { + qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestUUID); + } } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec40056299..51b78b6901 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -299,9 +299,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : AddressManager& addressManager = AddressManager::getInstance(); - // connect to the domainChangeRequired signal on AddressManager - connect(&addressManager, &AddressManager::possibleDomainChangeRequired, + // handle domain change signals from AddressManager + connect(&addressManager, &AddressManager::possibleDomainChangeRequiredToHostname, this, &Application::changeDomainHostname); + + connect(&addressManager, &AddressManager::possibleDomainChangeRequiredViaICEForID, + &domainHandler, &DomainHandler::setIceServerHostnameAndID); _settings = new QSettings(this); _numChangedSettings = 0; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 4509a1790c..a78e1e76a4 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -118,9 +118,21 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) { QJsonObject domainObject = dataObject[ADDRESS_API_DOMAIN_KEY].toObject(); const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address"; - QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); + const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address"; - emit possibleDomainChangeRequired(domainHostname); + if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { + QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); + + emit possibleDomainChangeRequiredToHostname(domainHostname); + } else { + QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); + + const QString DOMAIN_ID_KEY = "id"; + QString domainIDString = domainObject[DOMAIN_ID_KEY].toString(); + QUuid domainID(domainIDString); + + emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); + } // take the path that came back const QString LOCATION_KEY = "location"; @@ -182,7 +194,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) { QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive); if (hostnameRegex.indexIn(lookupString) != -1) { - emit possibleDomainChangeRequired(hostnameRegex.cap(0)); + emit possibleDomainChangeRequiredToHostname(hostnameRegex.cap(0)); emit lookupResultsFinished(); return true; } @@ -190,7 +202,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) { QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING); if (ipAddressRegex.indexIn(lookupString) != -1) { - emit possibleDomainChangeRequired(ipAddressRegex.cap(0)); + emit possibleDomainChangeRequiredToHostname(ipAddressRegex.cap(0)); emit lookupResultsFinished(); return true; } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index f7cc7c52ee..128c395cb4 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -40,7 +40,8 @@ signals: void lookupResultsFinished(); void lookupResultIsOffline(); void lookupResultIsNotFound(); - void possibleDomainChangeRequired(const QString& newHostname); + void possibleDomainChangeRequiredToHostname(const QString& newHostname); + void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID); void locationChangeRequired(const glm::vec3& newPosition, bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index b13f598a08..e5c8abb171 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -25,7 +25,7 @@ DomainHandler::DomainHandler(QObject* parent) : _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _assignmentUUID(), - _requiresICE(true), + _iceServerSockAddr(), _isConnected(false), _handshakeTimer(NULL), _settingsObject(), @@ -36,7 +36,7 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); - _requiresICE = true; + _iceServerSockAddr = HifiSockAddr(); _isConnected = false; emit disconnectedFromDomain(); @@ -125,6 +125,19 @@ void DomainHandler::setHostname(const QString& hostname) { } } +void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { + if (id != _uuid) { + // re-set the domain info to connect to new domain + hardReset(); + + _uuid = id; + _iceServerSockAddr = HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); + + qDebug() << "Domain ID changed to" << uuidStringWithoutCurlyBraces(_uuid) + << "- ICE required via ice server at" << iceServerHostname; + } +} + void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { for (int i = 0; i < hostInfo.addresses().size(); i++) { if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 9733a8bff3..ffd288d93d 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -54,7 +54,7 @@ public: const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - bool requiresICE() const { return _requiresICE; } + bool requiresICE() const { return !_iceServerSockAddr.isNull(); } bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); @@ -68,6 +68,7 @@ public: void softReset(); public slots: void setHostname(const QString& hostname); + void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); private slots: void completedHostnameLookup(const QHostInfo& hostInfo); @@ -87,7 +88,7 @@ private: QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; - bool _requiresICE; + HifiSockAddr _iceServerSockAddr; bool _isConnected; QTimer* _handshakeTimer; QJsonObject _settingsObject; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index afefd2a5c1..3af71546ce 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -242,63 +242,61 @@ void NodeList::sendDomainServerCheckIn() { // we don't know our public socket and we need to send it to the domain server // send a STUN request to figure it out sendSTUNRequest(); + } else if (!_domainHandler.isConnected() && _domainHandler.requiresICE()) { + sendICERequestForDomainConnection(); } else if (!_domainHandler.getIP().isNull()) { - if (!_domainHandler.isConnected() && _domainHandler.requiresICE()) { - sendICERequestForDomainConnection(); - } else { - bool isUsingDTLS = false; - - PacketType domainPacketType = !_domainHandler.isConnected() - ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; - - if (!_domainHandler.isConnected()) { - qDebug() << "Sending connect request to domain-server at" << _domainHandler.getHostname(); - } - - // construct the DS check in packet - QUuid packetUUID = _sessionUUID; - - if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) { - // this is a connect request and we're an assigned node - // so set our packetUUID as the assignment UUID - packetUUID = _domainHandler.getAssignmentUUID(); - } - - QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); - QDataStream packetStream(&domainServerPacket, QIODevice::Append); - - // pack our data to send to the domain-server - packetStream << _ownerType << _publicSockAddr - << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) - << (quint8) _nodeTypesOfInterest.size(); - - // copy over the bytes for node types of interest, if required - foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { - packetStream << nodeTypeOfInterest; - } - - if (!isUsingDTLS) { - writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid()); - } - - const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; - static unsigned int numDomainCheckins = 0; - - // send a STUN request every Nth domain server check in so we update our public socket, if required - if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { - sendSTUNRequest(); - } - - if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { - // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS - // so emit our signal that indicates that - emit limitOfSilentDomainCheckInsReached(); - } - - // increment the count of un-replied check-ins - _numNoReplyDomainCheckIns++; + bool isUsingDTLS = false; + + PacketType domainPacketType = !_domainHandler.isConnected() + ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; + + if (!_domainHandler.isConnected()) { + qDebug() << "Sending connect request to domain-server at" << _domainHandler.getHostname(); } + + // construct the DS check in packet + QUuid packetUUID = _sessionUUID; + + if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) { + // this is a connect request and we're an assigned node + // so set our packetUUID as the assignment UUID + packetUUID = _domainHandler.getAssignmentUUID(); + } + + QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); + QDataStream packetStream(&domainServerPacket, QIODevice::Append); + + // pack our data to send to the domain-server + packetStream << _ownerType << _publicSockAddr + << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) + << (quint8) _nodeTypesOfInterest.size(); + + // copy over the bytes for node types of interest, if required + foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { + packetStream << nodeTypeOfInterest; + } + + if (!isUsingDTLS) { + writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid()); + } + + const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; + static unsigned int numDomainCheckins = 0; + + // send a STUN request every Nth domain server check in so we update our public socket, if required + if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { + sendSTUNRequest(); + } + + if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS + // so emit our signal that indicates that + emit limitOfSilentDomainCheckInsReached(); + } + + // increment the count of un-replied check-ins + _numNoReplyDomainCheckIns++; } } From 6425276eb002629085fe8bc84b79d448ecae625f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 14:33:39 -0700 Subject: [PATCH 038/104] have domain-server using full networking heartbeat with ice-server --- domain-server/src/DomainServer.cpp | 31 ++++++++++++++++---- domain-server/src/DomainServer.h | 1 + libraries/networking/src/LimitedNodeList.cpp | 21 +++++++++++++ libraries/networking/src/LimitedNodeList.h | 2 ++ libraries/networking/src/NetworkPeer.h | 1 + libraries/networking/src/NodeList.cpp | 11 +------ 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c4d4e91600..78baf803fa 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -327,24 +327,39 @@ void DomainServer::setupAutomaticNetworking() { QString automaticNetworkValue = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); - if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { + if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || + automaticNetworkValue == FULL_AUTOMATIC_NETWORKING_VALUE) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); const QUuid& domainID = nodeList->getSessionUUID(); if (!domainID.isNull()) { qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID" - << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); + << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000; - // setup our timer to check our IP via stun every 30 seconds + // setup our timer to check our IP via stun every X seconds QTimer* dynamicIPTimer = new QTimer(this); connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); - dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); - // send public socket changes to the data server so nodes can find us at our new IP - connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); + if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { + dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); + + // send public socket changes to the data server so nodes can find us at our new IP + connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); + } else { + dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS); + + // setup a timer to heartbeat with the ice-server every so often + QTimer* iceHeartbeatTimer = new QTimer(this); + connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHearbeatToIceServer); + iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); + + // call our sendHeartbeaToIceServer immediately anytime a public address changes + connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer); + } // attempt to update our sockets now requestCurrentPublicSocketViaSTUN(); @@ -968,6 +983,10 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, domainUpdateJSON.toUtf8()); } +void DomainServer::sendHearbeatToIceServer() { + LimitedNodeList::getInstance()->sendHeartbeatToIceServer(); +} + void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index d6846fc610..d5e38acf89 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -64,6 +64,7 @@ private slots: void requestCurrentPublicSocketViaSTUN(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); + void sendHearbeatToIceServer(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 2c8e968375..b32fbe3b37 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -618,3 +618,24 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { return false; } + +void LimitedNodeList::sendHeartbeatToIceServer(QUuid headerID, const QUuid& connectionRequestID) { + + if (headerID.isNull()) { + headerID = _sessionUUID; + } + + QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat, headerID); + QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append); + + iceDataStream << _publicSockAddr << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()); + + if (!connectionRequestID.isNull()) { + iceDataStream << connectionRequestID; + + qDebug() << "Sending packet to ICE server to request connection info for peer with ID" + << uuidStringWithoutCurlyBraces(connectionRequestID); + } + + _nodeSocket.writeDatagram(iceRequestByteArray, QHostAddress::LocalHost, ICE_SERVER_DEFAULT_PORT); +} diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index b34845719c..1e97ba8190 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -106,6 +106,8 @@ public: virtual void sendSTUNRequest(); virtual bool processSTUNResponse(const QByteArray& packet); + + void sendHeartbeatToIceServer(QUuid headerID = QUuid(), const QUuid& connectRequestID = QUuid()); public slots: void reset(); void eraseAllNodes(); diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index ae1812cf87..da5850576b 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -19,6 +19,7 @@ const QString ICE_SERVER_HOSTNAME = "localhost"; const int ICE_SERVER_DEFAULT_PORT = 7337; +const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000; class NetworkPeer : public QObject { public: diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 3af71546ce..6d7670b077 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -304,16 +304,7 @@ void NodeList::sendICERequestForDomainConnection() { static QUuid iceUUID = QUuid::createUuid(); - QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat, iceUUID); - QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append); - - iceDataStream << _publicSockAddr << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()); - iceDataStream << _domainHandler.getUUID(); - - qDebug() << "Sending packet to ICE server to request connection info for peer with ID" - << uuidStringWithoutCurlyBraces(_domainHandler.getUUID()); - - _nodeSocket.writeDatagram(iceRequestByteArray, QHostAddress::LocalHost, ICE_SERVER_DEFAULT_PORT); + LimitedNodeList::sendHeartbeatToIceServer(iceUUID, _domainHandler.getUUID()); } int NodeList::processDomainServerList(const QByteArray& packet) { From 8cb258e14c5da72c227ef788822ce49bf6cb40bb Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 14:35:54 -0700 Subject: [PATCH 039/104] don't use just filename when searching in application framework --- interface/src/devices/SixenseManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index bd0bbe8a32..f5d838f95b 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -101,10 +101,10 @@ void SixenseManager::initialize() { _sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME); #else const QString SIXENSE_LIBRARY_NAME = "libsixense_x64"; - QFileInfo frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "../Frameworks/" + QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "../Frameworks/" + SIXENSE_LIBRARY_NAME; - _sixenseLibrary = new QLibrary(frameworkSixenseLibrary.fileName()); + _sixenseLibrary = new QLibrary(frameworkSixenseLibrary); #endif } From 970ba2296ac09266279692780734da5415c37e3f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 14:50:48 -0700 Subject: [PATCH 040/104] initial response from ice server to nodes --- ice-server/src/IceServer.cpp | 20 ++++++++++++++++---- ice-server/src/IceServer.h | 1 + libraries/networking/src/AccountManager.cpp | 2 +- libraries/networking/src/NetworkPeer.cpp | 16 ++++++++++++++++ libraries/networking/src/NetworkPeer.h | 2 ++ libraries/networking/src/NodeList.cpp | 4 ++++ libraries/networking/src/PacketHeaders.h | 6 ++++-- 7 files changed, 44 insertions(+), 7 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index a24abd3a49..a2e4221bfd 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -15,6 +15,7 @@ IceServer::IceServer(int argc, char* argv[]) : QCoreApplication(argc, argv), + _id(QUuid::createUuid()), _serverSocket() { // start the ice-server socket @@ -65,11 +66,22 @@ void IceServer::processDatagrams() { } // check if this node also included a UUID that they would like to connect to - QUuid connectRequestUUID; - hearbeatStream >> connectRequestUUID; + QUuid connectRequestID; + hearbeatStream >> connectRequestID; - if (!connectRequestUUID.isNull()) { - qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestUUID); + if (!connectRequestID.isNull()) { + qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestID); + + // check if we have that ID - if we do we can respond with their info, otherwise nothing we can do + SharedNetworkPeer matchingConectee = _activePeers.value(connectRequestID); + if (matchingConectee) { + QByteArray heartbeatResponse = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeatResponse, _id); + QDataStream responseStream(&heartbeatResponse, QIODevice::Append); + + responseStream << matchingConectee; + + _serverSocket.writeDatagram(heartbeatResponse, sendingSockAddr.getAddress(), sendingSockAddr.getPort()); + } } } } diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 3db9a6b2e2..a680bafbc3 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -24,6 +24,7 @@ public: private slots: void processDatagrams(); private: + QUuid _id; QHash _activePeers; QUdpSocket _serverSocket; }; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 595e4cbb60..7d924d02de 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -60,7 +60,7 @@ AccountManager::AccountManager() : _authURL(), _pendingCallbackMap(), _accountInfo(), - _shouldPersistToSettingsFile(false) + _shouldPersistToSettingsFile(true) { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 57a4a97331..2f94825fe5 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -65,6 +65,22 @@ void NetworkPeer::activateSymmetricSocket() { _activeSocket = &_symmetricSocket; } +QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer) { + out << peer._uuid; + out << peer._publicSocket; + out << peer._localSocket; + + return out; +} + +QDataStream& operator>>(QDataStream& in, NetworkPeer& peer) { + in >> peer._uuid; + in >> peer._publicSocket; + in >> peer._localSocket; + + return in; +} + QDebug operator<<(QDebug debug, const NetworkPeer &peer) { debug << uuidStringWithoutCurlyBraces(peer.getUUID()) << "- public:" << peer.getPublicSocket() diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index da5850576b..052040933b 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -41,6 +41,8 @@ public: void activateLocalSocket(); void activateSymmetricSocket(); + friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer); + friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer); protected: QUuid _uuid; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 6d7670b077..a1b6011032 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -123,6 +123,10 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr _domainHandler.parseDTLSRequirementPacket(packet); break; } + case PacketTypeIceServerHeartbeatResponse: { + + break; + } case PacketTypePing: { // send back a reply SharedNodePointer matchingNode = sendingNodeForPacket(packet); diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index aaab9c2928..dd1707521c 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -71,7 +71,8 @@ enum PacketType { PacketTypeParticleEditNack, PacketTypeEntityEditNack, // 48 PacketTypeSignedTransactionPayment, - PacketTypeIceServerHeartbeat + PacketTypeIceServerHeartbeat, + PacketTypeIceServerHeartbeatResponse }; typedef char PacketVersion; @@ -81,7 +82,8 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery - << PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack; + << PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack + << PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse; const int NUM_BYTES_MD5_HASH = 16; const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; From d26a94f84ca10ec2e73e28d3f3eca0af20834c78 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 15:00:59 -0700 Subject: [PATCH 041/104] remove inactive peers from memory in IceServer --- ice-server/src/IceServer.cpp | 31 ++++++++++++++++++++++++ ice-server/src/IceServer.h | 5 +++- libraries/networking/src/NetworkPeer.cpp | 7 +++++- libraries/networking/src/NetworkPeer.h | 9 +++++++ libraries/networking/src/Node.cpp | 3 --- libraries/networking/src/Node.h | 9 +------ 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index a2e4221bfd..be97df37a4 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -9,10 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include +#include #include "IceServer.h" +const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000; +const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; + IceServer::IceServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _id(QUuid::createUuid()), @@ -24,6 +30,12 @@ IceServer::IceServer(int argc, char* argv[]) : // call our process datagrams slot when the UDP socket has packets ready connect(&_serverSocket, &QUdpSocket::readyRead, this, &IceServer::processDatagrams); + + // setup our timer to clear inactive peers + QTimer* inactivePeerTimer = new QTimer(this); + connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers); + inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS); + } void IceServer::processDatagrams() { @@ -65,6 +77,9 @@ void IceServer::processDatagrams() { qDebug() << "Matched hearbeat to existing network peer" << *matchingPeer; } + // update our last heard microstamp for this network peer to now + matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); + // check if this node also included a UUID that they would like to connect to QUuid connectRequestID; hearbeatStream >> connectRequestID; @@ -86,3 +101,19 @@ void IceServer::processDatagrams() { } } } + +void IceServer::clearInactivePeers() { + NetworkPeerHash::iterator peerItem = _activePeers.begin(); + + while (peerItem != _activePeers.end()) { + SharedNetworkPeer peer = peerItem.value(); + + if ((usecTimestampNow() - peer->getLastHeardMicrostamp()) > (PEER_SILENCE_THRESHOLD_MSECS * 1000)) { + qDebug() << "Removing peer from memory for inactivity -" << *peer; + peerItem = _activePeers.erase(peerItem); + } else { + // we didn't kill this peer, push the iterator forwards + ++peerItem; + } + } +} diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index a680bafbc3..9c4c4242dc 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -18,14 +18,17 @@ #include +typedef QHash NetworkPeerHash; + class IceServer : public QCoreApplication { public: IceServer(int argc, char* argv[]); private slots: void processDatagrams(); + void clearInactivePeers(); private: QUuid _id; - QHash _activePeers; + NetworkPeerHash _activePeers; QUdpSocket _serverSocket; }; diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 2f94825fe5..1da6836bf9 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -9,6 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + +#include #include #include "NetworkPeer.h" @@ -18,7 +21,9 @@ NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, co _publicSocket(publicSocket), _localSocket(localSocket), _symmetricSocket(), - _activeSocket(NULL) + _activeSocket(NULL), + _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), + _lastHeardMicrostamp(usecTimestampNow()) { } diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 052040933b..dd32666b62 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -41,6 +41,12 @@ public: void activateLocalSocket(); void activateSymmetricSocket(); + quint64 getWakeTimestamp() const { return _wakeTimestamp; } + void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } + + quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } + void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } + friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer); friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer); protected: @@ -50,6 +56,9 @@ protected: HifiSockAddr _localSocket; HifiSockAddr _symmetricSocket; HifiSockAddr* _activeSocket; + + quint64 _wakeTimestamp; + quint64 _lastHeardMicrostamp; }; QDebug operator<<(QDebug debug, const NetworkPeer &peer); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 942f911f23..a25350c3f3 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -16,7 +16,6 @@ #include "SharedUtil.h" #include -#include #include const QString UNKNOWN_NodeType_t_NAME = "Unknown"; @@ -46,8 +45,6 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : NetworkPeer(uuid, publicSocket, localSocket), _type(type), - _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), - _lastHeardMicrostamp(usecTimestampNow()), _connectionSecret(), _bytesReceivedMovingAverage(NULL), _linkedData(NULL), diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index f300499add..238331947c 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -56,12 +56,6 @@ public: char getType() const { return _type; } void setType(char type) { _type = type; } - - quint64 getWakeTimestamp() const { return _wakeTimestamp; } - void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } - - quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } - void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } const QUuid& getConnectionSecret() const { return _connectionSecret; } void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } @@ -92,8 +86,7 @@ private: Node& operator=(Node otherNode); NodeType_t _type; - quint64 _wakeTimestamp; - quint64 _lastHeardMicrostamp; + QUuid _connectionSecret; SimpleMovingAverage* _bytesReceivedMovingAverage; From bfde23349fd732211b10833cfad3975998993e37 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 15:15:09 -0700 Subject: [PATCH 042/104] moving libraries style JS files to libraries --- examples/ControlACs.js | 2 +- examples/Recorder.js | 2 +- examples/editModels.js | 16 +- examples/frisbee.js | 2 +- examples/libraries/ExportMenu.js | 214 ++++++ examples/libraries/ModelImporter.js | 189 +++++ examples/libraries/ToolTip.js | 71 ++ examples/libraries/dataViewHelpers.js | 52 ++ examples/libraries/entityPropertyDialogBox.js | 255 +++++++ .../{ => libraries}/entitySelectionTool.js | 0 examples/libraries/httpMultiPart.js | 108 +++ examples/libraries/modelUploader.js | 690 ++++++++++++++++++ examples/libraries/progressDialog.js | 132 ++++ examples/libraries/stringHelpers.js | 66 ++ examples/{ => libraries}/toolBars.js | 0 15 files changed, 1781 insertions(+), 18 deletions(-) create mode 100644 examples/libraries/ExportMenu.js create mode 100644 examples/libraries/ModelImporter.js create mode 100644 examples/libraries/ToolTip.js create mode 100644 examples/libraries/dataViewHelpers.js create mode 100644 examples/libraries/entityPropertyDialogBox.js rename examples/{ => libraries}/entitySelectionTool.js (100%) create mode 100644 examples/libraries/httpMultiPart.js create mode 100644 examples/libraries/modelUploader.js create mode 100644 examples/libraries/progressDialog.js create mode 100644 examples/libraries/stringHelpers.js rename examples/{ => libraries}/toolBars.js (100%) diff --git a/examples/ControlACs.js b/examples/ControlACs.js index 1b60a9e848..4ccf5cff59 100644 --- a/examples/ControlACs.js +++ b/examples/ControlACs.js @@ -18,7 +18,7 @@ var controlVoxelSize = 0.25; var controlVoxelPosition = { x: 2000 , y: 0, z: 0 }; // Script. DO NOT MODIFY BEYOND THIS LINE. -Script.include("toolBars.js"); +Script.include("libraries/toolBars.js"); var DO_NOTHING = 0; var PLAY = 1; diff --git a/examples/Recorder.js b/examples/Recorder.js index 40bf2d2ed1..533f9f7fab 100644 --- a/examples/Recorder.js +++ b/examples/Recorder.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("toolBars.js"); +Script.include("libraries/toolBars.js"); var recordingFile = "recording.rec"; diff --git a/examples/editModels.js b/examples/editModels.js index 25c69f93f5..d8fa72ff60 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -22,10 +22,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("toolBars.js"); -Script.include("entitySelectionTool.js"); -var selectionDisplay = SelectionDisplay; - +Script.include("libraries/toolBars.js"); var windowDimensions = Controller.getViewportDimensions(); var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; var toolHeight = 50; @@ -2022,7 +2019,6 @@ function controller(wichSide) { if (this.glowedIntersectingModel.isKnownID) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); - selectionDisplay.hideSelection(this.glowedIntersectingModel); this.glowedIntersectingModel.isKnownID = false; } if (!this.grabbing) { @@ -2042,7 +2038,6 @@ function controller(wichSide) { if (wantEntityGlow) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); } - selectionDisplay.showSelection(this.glowedIntersectingModel, intersection.properties); } } } @@ -2113,7 +2108,6 @@ function controller(wichSide) { }); this.oldModelRotation = newRotation; this.oldModelPosition = newPosition; - selectionDisplay.showSelection(this.entityID, Entities.getEntityProperties(this.entityID)); var indicesToRemove = []; for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { @@ -2342,7 +2336,6 @@ function moveEntities() { }); - selectionDisplay.showSelection(leftController.entityID, Entities.getEntityProperties(leftController.entityID)); leftController.oldModelPosition = newPosition; leftController.oldModelRotation = rotation; leftController.oldModelHalfDiagonal *= ratio; @@ -2572,7 +2565,6 @@ function mousePressEvent(event) { print("Clicked on " + selectedEntityID.id + " " + entitySelected); tooltip.updateText(selectedEntityProperties); tooltip.show(true); - selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } } @@ -2591,7 +2583,6 @@ function mouseMoveEvent(event) { if (entityIntersection.accurate) { if(glowedEntityID.isKnownID && glowedEntityID.id != entityIntersection.entityID.id) { Entities.editEntity(glowedEntityID, { glowLevel: 0.0 }); - selectionDisplay.hideSelection(glowedEntityID); glowedEntityID.id = -1; glowedEntityID.isKnownID = false; } @@ -2609,7 +2600,6 @@ function mouseMoveEvent(event) { Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); } glowedEntityID = entityIntersection.entityID; - selectionDisplay.showSelection(entityIntersection.entityID, entityIntersection.properties); } } @@ -2732,7 +2722,6 @@ function mouseMoveEvent(event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); - selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } @@ -2807,7 +2796,6 @@ function scriptEnding() { cleanupModelMenus(); tooltip.cleanup(); modelImporter.cleanup(); - selectionDisplay.cleanup(); if (exportMenu) { exportMenu.close(); } @@ -3037,7 +3025,6 @@ Controller.keyPressEvent.connect(function (event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); somethingChanged = true; - selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } }); @@ -3155,7 +3142,6 @@ Window.nonBlockingFormClosed.connect(function() { properties.color.blue = array[index++].value; } Entities.editEntity(editModelID, properties); - selectionDisplay.showSelection(editModelID, propeties); } modelSelected = false; }); diff --git a/examples/frisbee.js b/examples/frisbee.js index e893a29309..c069d03275 100644 --- a/examples/frisbee.js +++ b/examples/frisbee.js @@ -14,7 +14,7 @@ // 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("toolBars.js"); +Script.include("libraries/toolBars.js"); const LEFT_PALM = 0; const LEFT_TIP = 1; diff --git a/examples/libraries/ExportMenu.js b/examples/libraries/ExportMenu.js new file mode 100644 index 0000000000..fb148b8150 --- /dev/null +++ b/examples/libraries/ExportMenu.js @@ -0,0 +1,214 @@ +var ExportMenu = function (opts) { + var self = this; + + var windowDimensions = Controller.getViewportDimensions(); + var pos = { x: windowDimensions.x / 2, y: windowDimensions.y - 100 }; + + this._onClose = opts.onClose || function () { }; + this._position = { x: 0.0, y: 0.0, z: 0.0 }; + this._scale = 1.0; + + var minScale = 1; + var maxScale = 32768; + var titleWidth = 120; + var locationWidth = 100; + var scaleWidth = 144; + var exportWidth = 100; + var cancelWidth = 100; + var margin = 4; + var height = 30; + var outerHeight = height + (2 * margin); + var buttonColor = { red: 128, green: 128, blue: 128 }; + + var SCALE_MINUS = scaleWidth * 40.0 / 100.0; + var SCALE_PLUS = scaleWidth * 63.0 / 100.0; + + var fullWidth = locationWidth + scaleWidth + exportWidth + cancelWidth + (2 * margin); + var offset = fullWidth / 2; + pos.x -= offset; + + var background = Overlays.addOverlay("text", { + x: pos.x, + y: pos.y, + opacity: 1, + width: fullWidth, + height: outerHeight, + backgroundColor: { red: 200, green: 200, blue: 200 }, + text: "", + }); + + var titleText = Overlays.addOverlay("text", { + x: pos.x, + y: pos.y - height, + font: { size: 14 }, + width: titleWidth, + height: height, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 255, green: 255, blue: 255 }, + text: "Export Models" + }); + + var locationButton = Overlays.addOverlay("text", { + x: pos.x + margin, + y: pos.y + margin, + width: locationWidth, + height: height, + color: { red: 255, green: 255, blue: 255 }, + text: "0, 0, 0", + }); + var scaleOverlay = Overlays.addOverlay("image", { + x: pos.x + margin + locationWidth, + y: pos.y + margin, + width: scaleWidth, + height: height, + subImage: { x: 0, y: 3, width: 144, height: height }, + imageURL: toolIconUrl + "voxel-size-selector.svg", + alpha: 0.9, + }); + var scaleViewWidth = 40; + var scaleView = Overlays.addOverlay("text", { + x: pos.x + margin + locationWidth + SCALE_MINUS, + y: pos.y + margin, + width: scaleViewWidth, + height: height, + alpha: 0.0, + color: { red: 255, green: 255, blue: 255 }, + text: "1" + }); + var exportButton = Overlays.addOverlay("text", { + x: pos.x + margin + locationWidth + scaleWidth, + y: pos.y + margin, + width: exportWidth, + height: height, + color: { red: 0, green: 255, blue: 255 }, + text: "Export" + }); + var cancelButton = Overlays.addOverlay("text", { + x: pos.x + margin + locationWidth + scaleWidth + exportWidth, + y: pos.y + margin, + width: cancelWidth, + height: height, + color: { red: 255, green: 255, blue: 255 }, + text: "Cancel" + }); + + var voxelPreview = Overlays.addOverlay("cube", { + position: { x: 0, y: 0, z: 0 }, + size: this._scale, + color: { red: 255, green: 255, blue: 0 }, + alpha: 1, + solid: false, + visible: true, + lineWidth: 4 + }); + + this.parsePosition = function (str) { + var parts = str.split(','); + if (parts.length == 3) { + var x = parseFloat(parts[0]); + var y = parseFloat(parts[1]); + var z = parseFloat(parts[2]); + if (isFinite(x) && isFinite(y) && isFinite(z)) { + return { x: x, y: y, z: z }; + } + } + return null; + }; + + this.showPositionPrompt = function () { + var positionStr = self._position.x + ", " + self._position.y + ", " + self._position.z; + while (1) { + positionStr = Window.prompt("Position to export form:", positionStr); + if (positionStr == null) { + break; + } + var position = self.parsePosition(positionStr); + if (position != null) { + self.setPosition(position.x, position.y, position.z); + break; + } + Window.alert("The position you entered was invalid."); + } + }; + + this.setScale = function (scale) { + self._scale = Math.min(maxScale, Math.max(minScale, scale)); + Overlays.editOverlay(scaleView, { text: self._scale }); + Overlays.editOverlay(voxelPreview, { size: self._scale }); + } + + this.decreaseScale = function () { + self.setScale(self._scale /= 2); + } + + this.increaseScale = function () { + self.setScale(self._scale *= 2); + } + + this.exportEntities = function() { + var x = self._position.x; + var y = self._position.y; + var z = self._position.z; + var s = self._scale; + var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo"; + filename = Window.save("Select where to save", filename, "*.svo") + if (filename) { + var success = Clipboard.exportEntities(filename, x, y, z, s); + if (!success) { + Window.alert("Export failed: no models found in selected area."); + } + } + self.close(); + }; + + this.getPosition = function () { + return self._position; + }; + + this.setPosition = function (x, y, z) { + self._position = { x: x, y: y, z: z }; + var positionStr = x + ", " + y + ", " + z; + Overlays.editOverlay(locationButton, { text: positionStr }); + Overlays.editOverlay(voxelPreview, { position: self._position }); + + }; + + this.mouseReleaseEvent = function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (clickedOverlay == locationButton) { + self.showPositionPrompt(); + } else if (clickedOverlay == exportButton) { + self.exportEntities(); + } else if (clickedOverlay == cancelButton) { + self.close(); + } else if (clickedOverlay == scaleOverlay) { + var x = event.x - pos.x - margin - locationWidth; + print(x); + if (x < SCALE_MINUS) { + self.decreaseScale(); + } else if (x > SCALE_PLUS) { + self.increaseScale(); + } + } + }; + + this.close = function () { + this.cleanup(); + this._onClose(); + }; + + this.cleanup = function () { + Overlays.deleteOverlay(background); + Overlays.deleteOverlay(titleText); + Overlays.deleteOverlay(locationButton); + Overlays.deleteOverlay(exportButton); + Overlays.deleteOverlay(cancelButton); + Overlays.deleteOverlay(voxelPreview); + Overlays.deleteOverlay(scaleOverlay); + Overlays.deleteOverlay(scaleView); + }; + + print("CONNECTING!"); + Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); +}; diff --git a/examples/libraries/ModelImporter.js b/examples/libraries/ModelImporter.js new file mode 100644 index 0000000000..868d6d7d46 --- /dev/null +++ b/examples/libraries/ModelImporter.js @@ -0,0 +1,189 @@ +ModelImporter = function (opts) { + var self = this; + + var windowDimensions = Controller.getViewportDimensions(); + + var height = 30; + var margin = 4; + var outerHeight = height + (2 * margin); + var titleWidth = 120; + var cancelWidth = 100; + var fullWidth = titleWidth + cancelWidth + (2 * margin); + + var localModels = Overlays.addOverlay("localmodels", { + position: { x: 1, y: 1, z: 1 }, + scale: 1, + visible: false + }); + var importScale = 1; + var importBoundaries = Overlays.addOverlay("cube", { + position: { x: 0, y: 0, z: 0 }, + size: 1, + color: { red: 128, blue: 128, green: 128 }, + lineWidth: 4, + solid: false, + visible: false + }); + + var pos = { x: windowDimensions.x / 2 - (fullWidth / 2), y: windowDimensions.y - 100 }; + + var background = Overlays.addOverlay("text", { + x: pos.x, + y: pos.y, + opacity: 1, + width: fullWidth, + height: outerHeight, + backgroundColor: { red: 200, green: 200, blue: 200 }, + visible: false, + text: "", + }); + + var titleText = Overlays.addOverlay("text", { + x: pos.x + margin, + y: pos.y + margin, + font: { size: 14 }, + width: titleWidth, + height: height, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 255, green: 255, blue: 255 }, + visible: false, + text: "Import Models" + }); + var cancelButton = Overlays.addOverlay("text", { + x: pos.x + margin + titleWidth, + y: pos.y + margin, + width: cancelWidth, + height: height, + color: { red: 255, green: 255, blue: 255 }, + visible: false, + text: "Close" + }); + this._importing = false; + + this.setImportVisible = function (visible) { + Overlays.editOverlay(importBoundaries, { visible: visible }); + Overlays.editOverlay(localModels, { visible: visible }); + Overlays.editOverlay(cancelButton, { visible: visible }); + Overlays.editOverlay(titleText, { visible: visible }); + Overlays.editOverlay(background, { visible: visible }); + }; + + var importPosition = { x: 0, y: 0, z: 0 }; + this.moveImport = function (position) { + importPosition = position; + Overlays.editOverlay(localModels, { + position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } + }); + Overlays.editOverlay(importBoundaries, { + position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } + }); + } + + this.mouseMoveEvent = function (event) { + if (self._importing) { + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Voxels.findRayIntersection(pickRay); + + var distance = 2;// * self._scale; + + if (false) {//intersection.intersects) { + var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection)); + if (intersectionDistance < distance) { + distance = intersectionDistance * 0.99; + } + + } + + var targetPosition = { + x: pickRay.origin.x + (pickRay.direction.x * distance), + y: pickRay.origin.y + (pickRay.direction.y * distance), + z: pickRay.origin.z + (pickRay.direction.z * distance) + }; + + if (targetPosition.x < 0) targetPosition.x = 0; + if (targetPosition.y < 0) targetPosition.y = 0; + if (targetPosition.z < 0) targetPosition.z = 0; + + var nudgeFactor = 1; + var newPosition = { + x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor, + y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor, + z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor + } + + self.moveImport(newPosition); + } + } + + this.mouseReleaseEvent = function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (clickedOverlay == cancelButton) { + self._importing = false; + self.setImportVisible(false); + } + }; + + // Would prefer to use {4} for the coords, but it would only capture the last digit. + var fileRegex = /__(.+)__(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)__/; + this.doImport = function () { + if (!self._importing) { + var filename = Window.browse("Select models to import", "", "*.svo") + if (filename) { + parts = fileRegex.exec(filename); + if (parts == null) { + Window.alert("The file you selected does not contain source domain or location information"); + } else { + var hostname = parts[1]; + var x = parts[2]; + var y = parts[3]; + var z = parts[4]; + var s = parts[5]; + importScale = s; + if (hostname != location.hostname) { + if (!Window.confirm(("These models were not originally exported from this domain. Continue?"))) { + return; + } + } else { + if (Window.confirm(("Would you like to import back to the source location?"))) { + var success = Clipboard.importEntities(filename); + if (success) { + Clipboard.pasteEntities(x, y, z, 1); + } else { + Window.alert("There was an error importing the entity file."); + } + return; + } + } + } + var success = Clipboard.importEntities(filename); + if (success) { + self._importing = true; + self.setImportVisible(true); + Overlays.editOverlay(importBoundaries, { size: s }); + } else { + Window.alert("There was an error importing the entity file."); + } + } + } + } + + this.paste = function () { + if (self._importing) { + // self._importing = false; + // self.setImportVisible(false); + Clipboard.pasteEntities(importPosition.x, importPosition.y, importPosition.z, 1); + } + } + + this.cleanup = function () { + Overlays.deleteOverlay(localModels); + Overlays.deleteOverlay(importBoundaries); + Overlays.deleteOverlay(cancelButton); + Overlays.deleteOverlay(titleText); + Overlays.deleteOverlay(background); + } + + Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); + Controller.mouseMoveEvent.connect(this.mouseMoveEvent); +}; diff --git a/examples/libraries/ToolTip.js b/examples/libraries/ToolTip.js new file mode 100644 index 0000000000..0070f4676f --- /dev/null +++ b/examples/libraries/ToolTip.js @@ -0,0 +1,71 @@ +function Tooltip() { + this.x = 285; + this.y = 115; + this.width = 500; + this.height = 180; // 145; + this.margin = 5; + this.decimals = 3; + + this.textOverlay = Overlays.addOverlay("text", { + x: this.x, + y: this.y, + width: this.width, + height: this.height, + margin: this.margin, + text: "", + color: { red: 128, green: 128, blue: 128 }, + alpha: 0.2, + visible: false + }); + this.show = function (doShow) { + Overlays.editOverlay(this.textOverlay, { visible: doShow }); + } + this.updateText = function(properties) { + var angles = Quat.safeEulerAngles(properties.rotation); + var text = "Entity Properties:\n" + text += "type: " + properties.type + "\n" + text += "X: " + properties.position.x.toFixed(this.decimals) + "\n" + text += "Y: " + properties.position.y.toFixed(this.decimals) + "\n" + text += "Z: " + properties.position.z.toFixed(this.decimals) + "\n" + text += "Pitch: " + angles.x.toFixed(this.decimals) + "\n" + text += "Yaw: " + angles.y.toFixed(this.decimals) + "\n" + text += "Roll: " + angles.z.toFixed(this.decimals) + "\n" + text += "Dimensions: " + properties.dimensions.x.toFixed(this.decimals) + ", " + + properties.dimensions.y.toFixed(this.decimals) + ", " + + properties.dimensions.z.toFixed(this.decimals) + "\n"; + + text += "Natural Dimensions: " + properties.naturalDimensions.x.toFixed(this.decimals) + ", " + + properties.naturalDimensions.y.toFixed(this.decimals) + ", " + + properties.naturalDimensions.z.toFixed(this.decimals) + "\n"; + + text += "ID: " + properties.id + "\n" + if (properties.type == "Model") { + text += "Model URL: " + properties.modelURL + "\n" + text += "Animation URL: " + properties.animationURL + "\n" + text += "Animation is playing: " + properties.animationIsPlaying + "\n" + if (properties.sittingPoints && properties.sittingPoints.length > 0) { + text += properties.sittingPoints.length + " Sitting points: " + for (var i = 0; i < properties.sittingPoints.length; ++i) { + text += properties.sittingPoints[i].name + " " + } + } else { + text += "No sitting points" + "\n" + } + } + if (properties.lifetime > -1) { + text += "Lifetime: " + properties.lifetime + "\n" + } + text += "Age: " + properties.ageAsText + "\n" + text += "Mass: " + properties.mass + "\n" + text += "Script: " + properties.script + "\n" + + + Overlays.editOverlay(this.textOverlay, { text: text }); + } + + this.cleanup = function () { + Overlays.deleteOverlay(this.textOverlay); + } +} + +tooltip = new Tooltip(); diff --git a/examples/libraries/dataViewHelpers.js b/examples/libraries/dataViewHelpers.js new file mode 100644 index 0000000000..6e60ba8bdc --- /dev/null +++ b/examples/libraries/dataViewHelpers.js @@ -0,0 +1,52 @@ +if (typeof DataView.prototype.indexOf !== "function") { + DataView.prototype.indexOf = function (searchString, position) { + var searchLength = searchString.length, + byteArrayLength = this.byteLength, + maxSearchIndex = byteArrayLength - searchLength, + searchCharCodes = [], + found, + i, + j; + + searchCharCodes[searchLength] = 0; + for (j = 0; j < searchLength; j += 1) { + searchCharCodes[j] = searchString.charCodeAt(j); + } + + i = position; + found = false; + while (i < maxSearchIndex && !found) { + j = 0; + while (j < searchLength && this.getUint8(i + j) === searchCharCodes[j]) { + j += 1; + } + found = (j === searchLength); + i += 1; + } + + return found ? i - 1 : -1; + }; +} + +if (typeof DataView.prototype.string !== "function") { + DataView.prototype.string = function (start, length) { + var charCodes = [], + end, + i; + + if (start === undefined) { + start = 0; + } + if (length === undefined) { + length = this.length; + } + + end = start + length; + for (i = start; i < end; i += 1) { + charCodes.push(this.getUint8(i)); + } + + return String.fromCharCode.apply(String, charCodes); + }; +} + diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js new file mode 100644 index 0000000000..fbac30e796 --- /dev/null +++ b/examples/libraries/entityPropertyDialogBox.js @@ -0,0 +1,255 @@ +// +// entityPropertyDialogBox.js +// examples +// +// Created by Brad hefta-Gaub on 10/1/14. +// Copyright 2014 High Fidelity, Inc. +// +// This script implements a class useful for building tools for editing entities. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +EntityPropertyDialogBox = (function () { + var that = {}; + + var propertiesForEditedEntity; + var editEntityFormArray; + var decimals = 3; + var dimensionX; + var dimensionY; + var dimensionZ; + var rescalePercentage; + var editModelID = -1; + + that.cleanup = function () { + }; + + that.openDialog = function (entityID) { + print(" Edit Properties.... about to edit properties..."); + + editModelID = entityID; + propertiesForEditedEntity = Entities.getEntityProperties(editModelID); + var properties = propertiesForEditedEntity; + + var array = new Array(); + var index = 0; + if (properties.type == "Model") { + array.push({ label: "Model URL:", value: properties.modelURL }); + index++; + array.push({ label: "Animation URL:", value: properties.animationURL }); + index++; + array.push({ label: "Animation is playing:", value: properties.animationIsPlaying }); + index++; + array.push({ label: "Animation FPS:", value: properties.animationFPS }); + index++; + array.push({ label: "Animation Frame:", value: properties.animationFrameIndex }); + index++; + } + array.push({ label: "Position:", type: "header" }); + index++; + array.push({ label: "X:", value: properties.position.x.toFixed(decimals) }); + index++; + array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) }); + index++; + array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) }); + index++; + + array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) }); + index++; + array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) }); + index++; + array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) }); + index++; + + array.push({ label: "Rotation:", type: "header" }); + index++; + var angles = Quat.safeEulerAngles(properties.rotation); + array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) }); + index++; + array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) }); + index++; + array.push({ label: "Roll:", value: angles.z.toFixed(decimals) }); + index++; + + array.push({ label: "Dimensions:", type: "header" }); + index++; + array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) }); + dimensionX = index; + index++; + array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) }); + dimensionY = index; + index++; + array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) }); + dimensionZ = index; + index++; + array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" }); + index++; + array.push({ label: "Rescale Percentage:", value: 100 }); + rescalePercentage = index; + index++; + array.push({ label: "", type: "inlineButton", buttonLabel: "Rescale", name: "rescaleDimensions" }); + index++; + + array.push({ label: "Velocity:", type: "header" }); + index++; + array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) }); + index++; + array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) }); + index++; + array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) }); + index++; + array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) }); + index++; + array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) }); + index++; + array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) }); + index++; + array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) }); + index++; + array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) }); + index++; + + array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) }); + index++; + array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) }); + index++; + array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) }); + index++; + + array.push({ label: "Collisions:", type: "header" }); + index++; + array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) }); + index++; + array.push({ label: "Ignore for Collisions:", value: properties.ignoreForCollisions }); + index++; + array.push({ label: "Collisions Will Move:", value: properties.collisionsWillMove }); + index++; + + array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) }); + index++; + + array.push({ label: "Visible:", value: properties.visible }); + index++; + + if (properties.type == "Box" || properties.type == "Sphere") { + array.push({ label: "Color:", type: "header" }); + index++; + array.push({ label: "Red:", value: properties.color.red }); + index++; + array.push({ label: "Green:", value: properties.color.green }); + index++; + array.push({ label: "Blue:", value: properties.color.blue }); + index++; + } + array.push({ button: "Cancel" }); + index++; + + editEntityFormArray = array; + Window.nonBlockingForm("Edit Properties", array); + }; + + Window.inlineButtonClicked.connect(function (name) { + if (name == "resetDimensions") { + Window.reloadNonBlockingForm([ + { value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX }, + { value: propertiesForEditedEntity.naturalDimensions.y.toFixed(decimals), oldIndex: dimensionY }, + { value: propertiesForEditedEntity.naturalDimensions.z.toFixed(decimals), oldIndex: dimensionZ } + ]); + } + + if (name == "rescaleDimensions") { + var peekValues = editEntityFormArray; + Window.peekNonBlockingFormResult(peekValues); + var peekX = peekValues[dimensionX].value; + var peekY = peekValues[dimensionY].value; + var peekZ = peekValues[dimensionZ].value; + var peekRescale = peekValues[rescalePercentage].value; + var rescaledX = peekX * peekRescale / 100.0; + var rescaledY = peekY * peekRescale / 100.0; + var rescaledZ = peekZ * peekRescale / 100.0; + + Window.reloadNonBlockingForm([ + { value: rescaledX.toFixed(decimals), oldIndex: dimensionX }, + { value: rescaledY.toFixed(decimals), oldIndex: dimensionY }, + { value: rescaledZ.toFixed(decimals), oldIndex: dimensionZ }, + { value: 100, oldIndex: rescalePercentage } + ]); + } + + }); + Window.nonBlockingFormClosed.connect(function() { + array = editEntityFormArray; + if (Window.getNonBlockingFormResult(array)) { + var properties = propertiesForEditedEntity; + var index = 0; + if (properties.type == "Model") { + properties.modelURL = array[index++].value; + properties.animationURL = array[index++].value; + properties.animationIsPlaying = array[index++].value; + properties.animationFPS = array[index++].value; + properties.animationFrameIndex = array[index++].value; + } + index++; // skip header + properties.position.x = array[index++].value; + properties.position.y = array[index++].value; + properties.position.z = array[index++].value; + properties.registrationPoint.x = array[index++].value; + properties.registrationPoint.y = array[index++].value; + properties.registrationPoint.z = array[index++].value; + + index++; // skip header + var angles = Quat.safeEulerAngles(properties.rotation); + angles.x = array[index++].value; + angles.y = array[index++].value; + angles.z = array[index++].value; + properties.rotation = Quat.fromVec3Degrees(angles); + + index++; // skip header + properties.dimensions.x = array[index++].value; + properties.dimensions.y = array[index++].value; + properties.dimensions.z = array[index++].value; + index++; // skip reset button + index++; // skip rescale percentage + index++; // skip rescale button + + index++; // skip header + properties.velocity.x = array[index++].value; + properties.velocity.y = array[index++].value; + properties.velocity.z = array[index++].value; + properties.damping = array[index++].value; + + properties.angularVelocity.x = array[index++].value; + properties.angularVelocity.y = array[index++].value; + properties.angularVelocity.z = array[index++].value; + properties.angularDamping = array[index++].value; + + properties.gravity.x = array[index++].value; + properties.gravity.y = array[index++].value; + properties.gravity.z = array[index++].value; + + index++; // skip header + properties.mass = array[index++].value; + properties.ignoreForCollisions = array[index++].value; + properties.collisionsWillMove = array[index++].value; + + properties.lifetime = array[index++].value; + properties.visible = array[index++].value; + + if (properties.type == "Box" || properties.type == "Sphere") { + index++; // skip header + properties.color.red = array[index++].value; + properties.color.green = array[index++].value; + properties.color.blue = array[index++].value; + } + Entities.editEntity(editModelID, properties); + selectionDisplay.highlightSelectable(editModelID, propeties); + } + modelSelected = false; + }); + + return that; + +}()); + diff --git a/examples/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js similarity index 100% rename from examples/entitySelectionTool.js rename to examples/libraries/entitySelectionTool.js diff --git a/examples/libraries/httpMultiPart.js b/examples/libraries/httpMultiPart.js new file mode 100644 index 0000000000..72f3bf6417 --- /dev/null +++ b/examples/libraries/httpMultiPart.js @@ -0,0 +1,108 @@ +httpMultiPart = (function () { + var that = {}, + parts, + byteLength, + boundaryString, + crlf; + + function clear() { + boundaryString = "--boundary_" + String(Uuid.generate()).slice(1, 36) + "="; + parts = []; + byteLength = 0; + crlf = ""; + } + that.clear = clear; + + function boundary() { + return boundaryString.slice(2); + } + that.boundary = boundary; + + function length() { + return byteLength; + } + that.length = length; + + function add(object) { + // - name, string + // - name, buffer + var buffer, + string, + stringBuffer, + compressedBuffer; + + if (object.name === undefined) { + + throw new Error("Item to add to HttpMultiPart must have a name"); + + } else if (object.string !== undefined) { + //--= + //Content-Disposition: form-data; name="model_name" + // + // + + string = crlf + boundaryString + "\r\n" + + "Content-Disposition: form-data; name=\"" + object.name + "\"\r\n" + + "\r\n" + + object.string; + buffer = string.toArrayBuffer(); + + } else if (object.buffer !== undefined) { + //--= + //Content-Disposition: form-data; name="fbx"; filename="" + //Content-Type: application/octet-stream + // + // + + string = crlf + boundaryString + "\r\n" + + "Content-Disposition: form-data; name=\"" + object.name + + "\"; filename=\"" + object.buffer.filename + "\"\r\n" + + "Content-Type: application/octet-stream\r\n" + + "\r\n"; + stringBuffer = string.toArrayBuffer(); + + compressedBuffer = object.buffer.buffer.compress(); + buffer = new Uint8Array(stringBuffer.byteLength + compressedBuffer.byteLength); + buffer.set(new Uint8Array(stringBuffer)); + buffer.set(new Uint8Array(compressedBuffer), stringBuffer.byteLength); + + } else { + + throw new Error("Item to add to HttpMultiPart not recognized"); + } + + byteLength += buffer.byteLength; + parts.push(buffer); + + crlf = "\r\n"; + + return true; + } + that.add = add; + + function response() { + var buffer, + index, + str, + i; + + str = crlf + boundaryString + "--\r\n"; + buffer = str.toArrayBuffer(); + byteLength += buffer.byteLength; + parts.push(buffer); + + buffer = new Uint8Array(byteLength); + index = 0; + for (i = 0; i < parts.length; i += 1) { + buffer.set(new Uint8Array(parts[i]), index); + index += parts[i].byteLength; + } + + return buffer; + } + that.response = response; + + clear(); + + return that; +}()); \ No newline at end of file diff --git a/examples/libraries/modelUploader.js b/examples/libraries/modelUploader.js new file mode 100644 index 0000000000..fb3db0958f --- /dev/null +++ b/examples/libraries/modelUploader.js @@ -0,0 +1,690 @@ +modelUploader = (function () { + var that = {}, + modelFile, + modelName, + modelURL, + modelCallback, + isProcessing, + fstBuffer, + fbxBuffer, + //svoBuffer, + mapping, + geometry, + API_URL = "https://data.highfidelity.io/api/v1/models", + MODEL_URL = "http://public.highfidelity.io/models/content", + NAME_FIELD = "name", + SCALE_FIELD = "scale", + FILENAME_FIELD = "filename", + TEXDIR_FIELD = "texdir", + MAX_TEXTURE_SIZE = 1024; + + function info(message) { + if (progressDialog.isOpen()) { + progressDialog.update(message); + } else { + progressDialog.open(message); + } + print(message); + } + + function error(message) { + if (progressDialog.isOpen()) { + progressDialog.close(); + } + print(message); + Window.alert(message); + } + + function randomChar(length) { + var characters = "0123457689abcdefghijklmnopqrstuvwxyz", + string = "", + i; + + for (i = 0; i < length; i += 1) { + string += characters[Math.floor(Math.random() * 36)]; + } + + return string; + } + + function resetDataObjects() { + fstBuffer = null; + fbxBuffer = null; + //svoBuffer = null; + mapping = {}; + geometry = {}; + geometry.textures = []; + geometry.embedded = []; + } + + function readFile(filename) { + var url = "file:///" + filename, + req = new XMLHttpRequest(); + + req.open("GET", url, false); + req.responseType = "arraybuffer"; + req.send(); + if (req.status !== 200) { + error("Could not read file: " + filename + " : " + req.statusText); + return null; + } + + return { + filename: filename.fileName(), + buffer: req.response + }; + } + + function readMapping(buffer) { + var dv = new DataView(buffer.buffer), + lines, + line, + tokens, + i, + name, + value, + remainder, + existing; + + mapping = {}; // { name : value | name : { value : [remainder] } } + lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/); + for (i = 0; i < lines.length; i += 1) { + line = lines[i].trim(); + if (line.length > 0 && line[0] !== "#") { + tokens = line.split(/\s*=\s*/); + if (tokens.length > 1) { + name = tokens[0]; + value = tokens[1]; + if (tokens.length > 2) { + remainder = tokens.slice(2, tokens.length).join(" = "); + } else { + remainder = null; + } + if (tokens.length === 2 && mapping[name] === undefined) { + mapping[name] = value; + } else { + if (mapping[name] === undefined) { + mapping[name] = {}; + + } else if (typeof mapping[name] !== "object") { + existing = mapping[name]; + mapping[name] = { existing : null }; + } + + if (mapping[name][value] === undefined) { + mapping[name][value] = []; + } + mapping[name][value].push(remainder); + } + } + } + } + } + + function writeMapping(buffer) { + var name, + value, + remainder, + i, + string = ""; + + for (name in mapping) { + if (mapping.hasOwnProperty(name)) { + if (typeof mapping[name] === "object") { + for (value in mapping[name]) { + if (mapping[name].hasOwnProperty(value)) { + remainder = mapping[name][value]; + if (remainder === null) { + string += (name + " = " + value + "\n"); + } else { + for (i = 0; i < remainder.length; i += 1) { + string += (name + " = " + value + " = " + remainder[i] + "\n"); + } + } + } + } + } else { + string += (name + " = " + mapping[name] + "\n"); + } + } + } + + buffer.buffer = string.toArrayBuffer(); + } + + function readGeometry(fbxBuffer) { + var textures, + view, + index, + EOF, + previousNodeFilename; + + // Reference: + // http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ + + textures = {}; + view = new DataView(fbxBuffer.buffer); + EOF = false; + + function parseBinaryFBX() { + var endOffset, + numProperties, + propertyListLength, + nameLength, + name, + filename; + + endOffset = view.getUint32(index, true); + numProperties = view.getUint32(index + 4, true); + propertyListLength = view.getUint32(index + 8, true); + nameLength = view.getUint8(index + 12); + index += 13; + + if (endOffset === 0) { + return; + } + if (endOffset < index || endOffset > view.byteLength) { + EOF = true; + return; + } + + name = view.string(index, nameLength).toLowerCase(); + index += nameLength; + + if (name === "content" && previousNodeFilename !== "") { + // Blender 2.71 exporter "embeds" external textures as empty binary blobs so ignore these + if (propertyListLength > 5) { + geometry.embedded.push(previousNodeFilename); + } + } + + if (name === "relativefilename") { + filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName(); + if (!textures.hasOwnProperty(filename)) { + textures[filename] = ""; + geometry.textures.push(filename); + } + previousNodeFilename = filename; + } else { + previousNodeFilename = ""; + } + + index += (propertyListLength); + + while (index < endOffset && !EOF) { + parseBinaryFBX(); + } + } + + function readTextFBX() { + var line, + view, + viewLength, + charCode, + charCodes, + numCharCodes, + filename, + relativeFilename = "", + MAX_CHAR_CODES = 250; + + view = new Uint8Array(fbxBuffer.buffer); + viewLength = view.byteLength; + charCodes = []; + numCharCodes = 0; + + for (index = 0; index < viewLength; index += 1) { + charCode = view[index]; + if (charCode !== 9 && charCode !== 32) { + if (charCode === 10) { // EOL. Can ignore EOF. + line = String.fromCharCode.apply(String, charCodes).toLowerCase(); + // For embedded textures, "Content:" line immediately follows "RelativeFilename:" line. + if (line.slice(0, 8) === "content:" && relativeFilename !== "") { + geometry.embedded.push(relativeFilename); + } + if (line.slice(0, 17) === "relativefilename:") { + filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); + if (!textures.hasOwnProperty(filename)) { + textures[filename] = ""; + geometry.textures.push(filename); + } + relativeFilename = filename; + } else { + relativeFilename = ""; + } + charCodes = []; + numCharCodes = 0; + } else { + if (numCharCodes < MAX_CHAR_CODES) { // Only interested in start of line + charCodes.push(charCode); + numCharCodes += 1; + } + } + } + } + } + + if (view.string(0, 18) === "Kaydara FBX Binary") { + previousNodeFilename = ""; + + index = 27; + while (index < view.byteLength - 39 && !EOF) { + parseBinaryFBX(); + } + + } else { + + readTextFBX(); + + } + } + + function readModel() { + var fbxFilename, + //svoFilename, + fileType; + + info("Reading model file"); + print("Model file: " + modelFile); + + if (modelFile.toLowerCase().fileType() === "fst") { + fstBuffer = readFile(modelFile); + if (fstBuffer === null) { + return false; + } + readMapping(fstBuffer); + fileType = mapping[FILENAME_FIELD].toLowerCase().fileType(); + if (mapping.hasOwnProperty(FILENAME_FIELD)) { + if (fileType === "fbx") { + fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; + //} else if (fileType === "svo") { + // svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; + } else { + error("Unrecognized model type in FST file!"); + return false; + } + } else { + error("Model file name not found in FST file!"); + return false; + } + } else { + fstBuffer = { + filename: "Interface." + randomChar(6), // Simulate avatar model uploading behaviour + buffer: null + }; + + if (modelFile.toLowerCase().fileType() === "fbx") { + fbxFilename = modelFile; + mapping[FILENAME_FIELD] = modelFile.fileName(); + + //} else if (modelFile.toLowerCase().fileType() === "svo") { + // svoFilename = modelFile; + // mapping[FILENAME_FIELD] = modelFile.fileName(); + + } else { + error("Unrecognized file type: " + modelFile); + return false; + } + } + + if (!isProcessing) { return false; } + + if (fbxFilename) { + fbxBuffer = readFile(fbxFilename); + if (fbxBuffer === null) { + return false; + } + + if (!isProcessing) { return false; } + + readGeometry(fbxBuffer); + } + + //if (svoFilename) { + // svoBuffer = readFile(svoFilename); + // if (svoBuffer === null) { + // return false; + // } + //} + + // Add any missing basic mappings + if (!mapping.hasOwnProperty(NAME_FIELD)) { + mapping[NAME_FIELD] = modelFile.fileName().fileBase(); + } + if (!mapping.hasOwnProperty(TEXDIR_FIELD)) { + mapping[TEXDIR_FIELD] = "."; + } + if (!mapping.hasOwnProperty(SCALE_FIELD)) { + mapping[SCALE_FIELD] = 1.0; + } + + return true; + } + + function setProperties() { + var form = [], + directory, + displayAs, + validateAs; + + progressDialog.close(); + print("Setting model properties"); + + form.push({ label: "Name:", value: mapping[NAME_FIELD] }); + + directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD]; + displayAs = new RegExp("^" + modelFile.path().regExpEscape() + "[\\\\\\\/](.*)"); + validateAs = new RegExp("^" + modelFile.path().regExpEscape() + "([\\\\\\\/].*)?"); + + form.push({ + label: "Texture directory:", + directory: modelFile.path() + "/" + mapping[TEXDIR_FIELD], + title: "Choose Texture Directory", + displayAs: displayAs, + validateAs: validateAs, + errorMessage: "Texture directory must be subdirectory of the model directory." + }); + + form.push({ button: "Cancel" }); + + if (!Window.form("Set Model Properties", form)) { + print("User cancelled uploading model"); + return false; + } + + mapping[NAME_FIELD] = form[0].value; + mapping[TEXDIR_FIELD] = form[1].directory.slice(modelFile.path().length + 1); + if (mapping[TEXDIR_FIELD] === "") { + mapping[TEXDIR_FIELD] = "."; + } + + writeMapping(fstBuffer); + + return true; + } + + function createHttpMessage(callback) { + var multiparts = [], + lodCount, + lodFile, + lodBuffer, + textureBuffer, + textureSourceFormat, + textureTargetFormat, + embeddedTextures, + i; + + info("Preparing to send model"); + + // Model name + if (mapping.hasOwnProperty(NAME_FIELD)) { + multiparts.push({ + name : "model_name", + string : mapping[NAME_FIELD] + }); + } else { + error("Model name is missing"); + httpMultiPart.clear(); + return; + } + + // FST file + if (fstBuffer) { + multiparts.push({ + name : "fst", + buffer: fstBuffer + }); + } + + // FBX file + if (fbxBuffer) { + multiparts.push({ + name : "fbx", + buffer: fbxBuffer + }); + } + + // SVO file + //if (svoBuffer) { + // multiparts.push({ + // name : "svo", + // buffer: svoBuffer + // }); + //} + + // LOD files + lodCount = 0; + for (lodFile in mapping.lod) { + if (mapping.lod.hasOwnProperty(lodFile)) { + lodBuffer = readFile(modelFile.path() + "\/" + lodFile); + if (lodBuffer === null) { + return; + } + multiparts.push({ + name: "lod" + lodCount, + buffer: lodBuffer + }); + lodCount += 1; + } + if (!isProcessing) { return; } + } + + // Textures + embeddedTextures = "|" + geometry.embedded.join("|") + "|"; + for (i = 0; i < geometry.textures.length; i += 1) { + if (embeddedTextures.indexOf("|" + geometry.textures[i].fileName() + "|") === -1) { + textureBuffer = readFile(modelFile.path() + "\/" + + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") + + geometry.textures[i]); + if (textureBuffer === null) { + return; + } + + textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); + textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png"); + textureBuffer.buffer = + textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE); + textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat; + + multiparts.push({ + name: "texture" + i, + buffer: textureBuffer + }); + } + + if (!isProcessing) { return; } + } + + // Model category + multiparts.push({ + name : "model_category", + string : "content" + }); + + // Create HTTP message + httpMultiPart.clear(); + Script.setTimeout(function addMultipart() { + var multipart = multiparts.shift(); + httpMultiPart.add(multipart); + + if (!isProcessing) { return; } + + if (multiparts.length > 0) { + Script.setTimeout(addMultipart, 25); + } else { + callback(); + } + }, 25); + } + + function sendToHighFidelity() { + var req, + uploadedChecks, + HTTP_GET_TIMEOUT = 60, // 1 minute + HTTP_SEND_TIMEOUT = 900, // 15 minutes + UPLOADED_CHECKS = 30, + CHECK_UPLOADED_TIMEOUT = 1, // 1 second + handleCheckUploadedResponses, + handleUploadModelResponses, + handleRequestUploadResponses; + + function uploadTimedOut() { + error("Model upload failed: Internet request timed out!"); + } + + function debugResponse() { + print("req.errorCode = " + req.errorCode); + print("req.readyState = " + req.readyState); + print("req.status = " + req.status); + print("req.statusText = " + req.statusText); + print("req.responseType = " + req.responseType); + print("req.responseText = " + req.responseText); + print("req.response = " + req.response); + print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders()); + } + + function checkUploaded() { + if (!isProcessing) { return; } + + info("Checking uploaded model"); + + req = new XMLHttpRequest(); + req.open("HEAD", modelURL, true); + req.timeout = HTTP_GET_TIMEOUT * 1000; + req.onreadystatechange = handleCheckUploadedResponses; + req.ontimeout = uploadTimedOut; + req.send(); + } + + handleCheckUploadedResponses = function () { + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + // Note: Unlike avatar models, for content models we don't need to refresh texture cache. + print("Model uploaded: " + modelURL); + progressDialog.close(); + if (Window.confirm("Your model has been uploaded as: " + modelURL + "\nDo you want to rez it?")) { + modelCallback(modelURL); + } + } else if (req.status === 404) { + if (uploadedChecks > 0) { + uploadedChecks -= 1; + Script.setTimeout(checkUploaded, CHECK_UPLOADED_TIMEOUT * 1000); + } else { + print("Error: " + req.status + " " + req.statusText); + error("We could not verify that your model was successfully uploaded but it may have been at: " + + modelURL); + } + } else { + print("Error: " + req.status + " " + req.statusText); + error("There was a problem with your upload, please try again later."); + } + } + }; + + function uploadModel(method) { + var url; + + if (!isProcessing) { return; } + + req = new XMLHttpRequest(); + if (method === "PUT") { + url = API_URL + "\/" + modelName; + req.open("PUT", url, true); //print("PUT " + url); + } else { + url = API_URL; + req.open("POST", url, true); //print("POST " + url); + } + req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\""); + req.timeout = HTTP_SEND_TIMEOUT * 1000; + req.onreadystatechange = handleUploadModelResponses; + req.ontimeout = uploadTimedOut; + req.send(httpMultiPart.response().buffer); + } + + handleUploadModelResponses = function () { + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + uploadedChecks = UPLOADED_CHECKS; + checkUploaded(); + } else { + print("Error: " + req.status + " " + req.statusText); + error("There was a problem with your upload, please try again later."); + } + } + }; + + function requestUpload() { + var url; + + if (!isProcessing) { return; } + + url = API_URL + "\/" + modelName; // XMLHttpRequest automatically handles authorization of API requests. + req = new XMLHttpRequest(); + req.open("GET", url, true); //print("GET " + url); + req.responseType = "json"; + req.timeout = HTTP_GET_TIMEOUT * 1000; + req.onreadystatechange = handleRequestUploadResponses; + req.ontimeout = uploadTimedOut; + req.send(); + } + + handleRequestUploadResponses = function () { + var response; + + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + if (req.responseType === "json") { + response = JSON.parse(req.responseText); + if (response.status === "success") { + if (response.exists === false) { + uploadModel("POST"); + } else if (response.can_update === true) { + uploadModel("PUT"); + } else { + error("This model file already exists and is owned by someone else!"); + } + return; + } + } + } else { + print("Error: " + req.status + " " + req.statusText); + } + error("Model upload failed! Something went wrong at the data server."); + } + }; + + info("Sending model to High Fidelity"); + + requestUpload(); + } + + that.upload = function (file, callback) { + + modelFile = file; + modelCallback = callback; + + isProcessing = true; + + progressDialog.onCancel = function () { + print("User cancelled uploading model"); + isProcessing = false; + }; + + resetDataObjects(); + + if (readModel()) { + if (setProperties()) { + modelName = mapping[NAME_FIELD]; + modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST + + createHttpMessage(sendToHighFidelity); + } + } + + resetDataObjects(); + }; + + return that; +}()); \ No newline at end of file diff --git a/examples/libraries/progressDialog.js b/examples/libraries/progressDialog.js new file mode 100644 index 0000000000..349808131d --- /dev/null +++ b/examples/libraries/progressDialog.js @@ -0,0 +1,132 @@ +progressDialog = (function () { + var that = {}, + progressBackground, + progressMessage, + cancelButton, + displayed = false, + backgroundWidth = 300, + backgroundHeight = 100, + messageHeight = 32, + cancelWidth = 70, + cancelHeight = 32, + textColor = { red: 255, green: 255, blue: 255 }, + textBackground = { red: 52, green: 52, blue: 52 }, + backgroundUrl = toolIconUrl + "progress-background.svg", + windowDimensions; + + progressBackground = Overlays.addOverlay("image", { + width: backgroundWidth, + height: backgroundHeight, + imageURL: backgroundUrl, + alpha: 0.9, + visible: false + }); + + progressMessage = Overlays.addOverlay("text", { + width: backgroundWidth - 40, + height: messageHeight, + text: "", + textColor: textColor, + backgroundColor: textBackground, + alpha: 0.9, + visible: false + }); + + cancelButton = Overlays.addOverlay("text", { + width: cancelWidth, + height: cancelHeight, + text: "Cancel", + textColor: textColor, + backgroundColor: textBackground, + alpha: 0.9, + visible: false + }); + + function move() { + var progressX, + progressY; + + if (displayed) { + + if (windowDimensions.x === Window.innerWidth && windowDimensions.y === Window.innerHeight) { + return; + } + windowDimensions.x = Window.innerWidth; + windowDimensions.y = Window.innerHeight; + + progressX = (windowDimensions.x - backgroundWidth) / 2; // Center. + progressY = windowDimensions.y / 2 - backgroundHeight; // A little up from center. + + Overlays.editOverlay(progressBackground, { x: progressX, y: progressY }); + Overlays.editOverlay(progressMessage, { x: progressX + 20, y: progressY + 15 }); + Overlays.editOverlay(cancelButton, { + x: progressX + backgroundWidth - cancelWidth - 20, + y: progressY + backgroundHeight - cancelHeight - 15 + }); + } + } + that.move = move; + + that.onCancel = undefined; + + function open(message) { + if (!displayed) { + windowDimensions = { x: 0, y : 0 }; + displayed = true; + move(); + Overlays.editOverlay(progressBackground, { visible: true }); + Overlays.editOverlay(progressMessage, { visible: true, text: message }); + Overlays.editOverlay(cancelButton, { visible: true }); + } else { + throw new Error("open() called on progressDialog when already open"); + } + } + that.open = open; + + function isOpen() { + return displayed; + } + that.isOpen = isOpen; + + function update(message) { + if (displayed) { + Overlays.editOverlay(progressMessage, { text: message }); + } else { + throw new Error("update() called on progressDialog when not open"); + } + } + that.update = update; + + function close() { + if (displayed) { + Overlays.editOverlay(cancelButton, { visible: false }); + Overlays.editOverlay(progressMessage, { visible: false }); + Overlays.editOverlay(progressBackground, { visible: false }); + displayed = false; + } else { + throw new Error("close() called on progressDialog when not open"); + } + } + that.close = close; + + function mousePressEvent(event) { + if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === cancelButton) { + if (typeof this.onCancel === "function") { + close(); + this.onCancel(); + } + return true; + } + return false; + } + that.mousePressEvent = mousePressEvent; + + function cleanup() { + Overlays.deleteOverlay(cancelButton); + Overlays.deleteOverlay(progressMessage); + Overlays.deleteOverlay(progressBackground); + } + that.cleanup = cleanup; + + return that; +}()); \ No newline at end of file diff --git a/examples/libraries/stringHelpers.js b/examples/libraries/stringHelpers.js new file mode 100644 index 0000000000..0fae9035f0 --- /dev/null +++ b/examples/libraries/stringHelpers.js @@ -0,0 +1,66 @@ + + + +if (typeof String.prototype.fileName !== "function") { + String.prototype.fileName = function () { + return this.replace(/^(.*[\/\\])*/, ""); + }; +} + +if (typeof String.prototype.fileBase !== "function") { + String.prototype.fileBase = function () { + var filename = this.fileName(); + return filename.slice(0, filename.indexOf(".")); + }; +} + +if (typeof String.prototype.fileType !== "function") { + String.prototype.fileType = function () { + return this.slice(this.lastIndexOf(".") + 1); + }; +} + +if (typeof String.prototype.path !== "function") { + String.prototype.path = function () { + return this.replace(/[\\\/][^\\\/]*$/, ""); + }; +} + +if (typeof String.prototype.regExpEscape !== "function") { + String.prototype.regExpEscape = function () { + return this.replace(/([$\^.+*?|\\\/{}()\[\]])/g, '\\$1'); + }; +} + +if (typeof String.prototype.toArrayBuffer !== "function") { + String.prototype.toArrayBuffer = function () { + var length, + buffer, + view, + charCode, + charCodes, + i; + + charCodes = []; + + length = this.length; + for (i = 0; i < length; i += 1) { + charCode = this.charCodeAt(i); + if (charCode <= 255) { + charCodes.push(charCode); + } else { + charCodes.push(charCode / 256); + charCodes.push(charCode % 256); + } + } + + length = charCodes.length; + buffer = new ArrayBuffer(length); + view = new Uint8Array(buffer); + for (i = 0; i < length; i += 1) { + view[i] = charCodes[i]; + } + + return buffer; + }; +} diff --git a/examples/toolBars.js b/examples/libraries/toolBars.js similarity index 100% rename from examples/toolBars.js rename to examples/libraries/toolBars.js From 4ed047cf98dfb6a372169ef03182feff1777fcf1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 15:15:52 -0700 Subject: [PATCH 043/104] first pass at newEditEntities.js --- examples/libraries/entitySelectionTool.js | 102 ++-- examples/newEditEntities.js | 626 ++++++++++++++++++++++ 2 files changed, 686 insertions(+), 42 deletions(-) create mode 100644 examples/newEditEntities.js diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 1126275e32..1b97cc661f 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -13,6 +13,8 @@ SelectionDisplay = (function () { var that = {}; + + var currentSelection = { id: -1, isKnownID: false }; var handleHoverColor = { red: 224, green: 67, blue: 36 }; var handleHoverAlpha = 1.0; @@ -63,6 +65,18 @@ SelectionDisplay = (function () { lineWidth: grabberLineWidth, }; + + var highlightBox = Overlays.addOverlay("cube", { + position: { x:0, y: 0, z: 0}, + size: 1, + color: { red: 180, green: 180, blue: 180}, + alpha: 1, + solid: false, + visible: false, + dashed: true, + lineWidth: 1.0, + }); + var selectionBox = Overlays.addOverlay("cube", { position: { x:0, y: 0, z: 0}, size: 1, @@ -72,15 +86,6 @@ SelectionDisplay = (function () { visible: false, dashed: true, lineWidth: 1.0, - - /* - pulseMin: 0.0, - pulseMax: 1.0, - pulsePeriod: 2.0, - //glowLevelPulse: 1.0, - //alphaPulse: 0.5, - colorPulse: 1.0 - */ }); var grabberLBN = Overlays.addOverlay("cube", grabberPropertiesCorner); @@ -125,28 +130,6 @@ SelectionDisplay = (function () { rotation: baseOverlayRotation }); - /* NOTE: not currently in use - var baseOfEntityOverlay = Overlays.addOverlay("rectangle3d", { - position: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 0}, - alpha: 1, - solid: false, - visible: false, - lineWidth: 2.0, - isDashedLine: true - }); - var heightOfEntityOverlay = Overlays.addOverlay("line3d", { - position: { x:0, y: 0, z: 0}, - end: { x:0, y: 0, z: 0}, - color: { red: 0, green: 0, blue: 0}, - alpha: 1, - solid: false, - visible: false, - lineWidth: 2.0, - isDashedLine: true - }); - */ - var yawOverlayAngles = { x: 90, y: 0, z: 0 }; var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); var pitchOverlayAngles = { x: 0, y: 90, z: 0 }; @@ -221,6 +204,7 @@ SelectionDisplay = (function () { }); that.cleanup = function () { + Overlays.deleteOverlay(highlightBox); Overlays.deleteOverlay(selectionBox); Overlays.deleteOverlay(baseOfEntityProjectionOverlay); Overlays.deleteOverlay(grabberLBN); @@ -252,10 +236,6 @@ SelectionDisplay = (function () { Overlays.deleteOverlay(grabberEdgeFR); Overlays.deleteOverlay(grabberEdgeFL); - - //Overlays.deleteOverlay(baseOfEntityOverlay); - //Overlays.deleteOverlay(heightOfEntityOverlay); - Overlays.deleteOverlay(yawHandle); Overlays.deleteOverlay(pitchHandle); Overlays.deleteOverlay(rollHandle); @@ -266,8 +246,29 @@ SelectionDisplay = (function () { }; - that.showSelection = function (entityID, properties) { - + that.highlightSelectable = function(entityID) { + var properties = Entities.getEntityProperties(entityID); + var center = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; + Overlays.editOverlay(highlightBox, + { + visible: true, + position: center, + dimensions: properties.dimensions, + rotation: properties.rotation, + }); + }; + + that.unhighlightSelectable = function(entityID) { + Overlays.editOverlay(highlightBox,{ visible: false}); + }; + + that.select = function(entityID) { + var properties = Entities.getEntityProperties(entityID); + if (currentSelection.isKnownID == true) { + that.unselect(currentSelection); + } + currentSelection = entityID; + var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); var innerRadius = diagonal; @@ -496,7 +497,14 @@ SelectionDisplay = (function () { Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; - that.hideSelection = function (entityID) { + that.unselectAll = function () { + if (currentSelection.isKnownID == true) { + that.unselect(currentSelection); + } + currentSelection = { id: -1, isKnownID: false }; + }; + + that.unselect = function (entityID) { Overlays.editOverlay(selectionBox, { visible: false }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: false }); Overlays.editOverlay(grabberLBN, { visible: false }); @@ -528,10 +536,6 @@ SelectionDisplay = (function () { Overlays.editOverlay(grabberEdgeFR, { visible: false }); Overlays.editOverlay(grabberEdgeFL, { visible: false }); - // Not currently in use - //Overlays.editOverlay(baseOfEntityOverlay, { visible: false }); - //Overlays.editOverlay(heightOfEntityOverlay, { visible: false }); - Overlays.editOverlay(yawHandle, { visible: false }); Overlays.editOverlay(pitchHandle, { visible: false }); Overlays.editOverlay(rollHandle, { visible: false }); @@ -542,6 +546,20 @@ SelectionDisplay = (function () { Entities.editEntity(entityID, { localRenderAlpha: 1.0 }); }; + + + that.mousePressEvent = function(event) { + }; + + that.mouseMoveEvent = function(event) { + }; + + that.mouseReleaseEvent = function(event) { + }; + + Controller.mousePressEvent.connect(that.mousePressEvent); + Controller.mouseMoveEvent.connect(that.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); return that; diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js new file mode 100644 index 0000000000..bf74ebc2aa --- /dev/null +++ b/examples/newEditEntities.js @@ -0,0 +1,626 @@ +// +// newEditEntities.js +// examples +// +// Created by Brad Hefta-Gaub on 10/2/14. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to edit models either with the razor hydras or with your mouse +// +// If using the hydras : +// grab grab models with the triggers, you can then move the models around or scale them with both hands. +// You can switch mode using the bumpers so that you can move models around more easily. +// +// If using the mouse : +// - left click lets you move the model in the plane facing you. +// If pressing shift, it will move on the horizontal plane it's in. +// - right click lets you rotate the model. z and x give access to more axes of rotation while shift provides finer control. +// - left + right click lets you scale the model. +// - you can press r while holding the model to reset its rotation +// +// 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/stringHelpers.js"); +Script.include("libraries/dataviewHelpers.js"); +Script.include("libraries/httpMultiPart.js"); +Script.include("libraries/modelUploader.js"); +Script.include("libraries/toolBars.js"); +Script.include("libraries/progressDialog.js"); + +Script.include("libraries/entitySelectionTool.js"); +var selectionDisplay = SelectionDisplay; + +Script.include("libraries/ModelImporter.js"); +var modelImporter = new ModelImporter(); + +Script.include("libraries/ExportMenu.js"); +Script.include("libraries/ToolTip.js"); + +Script.include("libraries/entityPropertyDialogBox.js"); +var entityPropertyDialogBox = EntityPropertyDialogBox; + +var windowDimensions = Controller.getViewportDimensions(); +var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; +var toolHeight = 50; +var toolWidth = 50; + +var MIN_ANGULAR_SIZE = 2; +var MAX_ANGULAR_SIZE = 45; +var allowLargeModels = false; +var allowSmallModels = false; +var wantEntityGlow = false; + +var SPAWN_DISTANCE = 1; +var DEFAULT_DIMENSION = 0.20; + +var modelURLs = [ + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx" + ]; + +var mode = 0; +var isActive = false; + + +var toolBar = (function () { + var that = {}, + toolBar, + activeButton, + newModelButton, + newCubeButton, + newSphereButton, + browseModelsButton, + loadURLMenuItem, + loadFileMenuItem, + menuItemWidth = 125, + menuItemOffset, + menuItemHeight, + menuItemMargin = 5, + menuTextColor = { red: 255, green: 255, blue: 255 }, + menuBackgoundColor = { red: 18, green: 66, blue: 66 }; + + function initialize() { + toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); + + activeButton = toolBar.addTool({ + imageURL: toolIconUrl + "models-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }, true, false); + + newModelButton = toolBar.addTool({ + imageURL: toolIconUrl + "add-model-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }, true, false); + + browseModelsButton = toolBar.addTool({ + imageURL: toolIconUrl + "list-icon.svg", + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }); + + menuItemOffset = toolBar.height / 3 + 2; + menuItemHeight = Tool.IMAGE_HEIGHT / 2 - 2; + + loadURLMenuItem = Overlays.addOverlay("text", { + x: newModelButton.x - menuItemWidth, + y: newModelButton.y + menuItemOffset, + width: menuItemWidth, + height: menuItemHeight, + backgroundColor: menuBackgoundColor, + topMargin: menuItemMargin, + text: "Model URL", + alpha: 0.9, + visible: false + }); + + loadFileMenuItem = Overlays.addOverlay("text", { + x: newModelButton.x - menuItemWidth, + y: newModelButton.y + menuItemOffset + menuItemHeight, + width: menuItemWidth, + height: menuItemHeight, + backgroundColor: menuBackgoundColor, + topMargin: menuItemMargin, + text: "Model File", + alpha: 0.9, + visible: false + }); + + newCubeButton = toolBar.addTool({ + imageURL: toolIconUrl + "add-cube.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }); + + newSphereButton = toolBar.addTool({ + imageURL: toolIconUrl + "add-sphere.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }); + + } + + function toggleNewModelButton(active) { + if (active === undefined) { + active = !toolBar.toolSelected(newModelButton); + } + toolBar.selectTool(newModelButton, active); + + Overlays.editOverlay(loadURLMenuItem, { visible: active }); + Overlays.editOverlay(loadFileMenuItem, { visible: active }); + } + + function addModel(url) { + var position; + + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + + if (position.x > 0 && position.y > 0 && position.z > 0) { + Entities.addEntity({ + type: "Model", + position: position, + dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, + modelURL: url + }); + print("Model added: " + url); + } else { + print("Can't add model: Model would be out of bounds."); + } + } + + that.move = function () { + var newViewPort, + toolsX, + toolsY; + + newViewPort = Controller.getViewportDimensions(); + + if (toolBar === undefined) { + initialize(); + + } else if (windowDimensions.x === newViewPort.x && + windowDimensions.y === newViewPort.y) { + return; + } + + windowDimensions = newViewPort; + toolsX = windowDimensions.x - 8 - toolBar.width; + toolsY = (windowDimensions.y - toolBar.height) / 2; + + toolBar.move(toolsX, toolsY); + + Overlays.editOverlay(loadURLMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset }); + Overlays.editOverlay(loadFileMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset + menuItemHeight }); + }; + + that.mousePressEvent = function (event) { + var clickedOverlay, + url, + file; + + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (activeButton === toolBar.clicked(clickedOverlay)) { + isActive = !isActive; + return true; + } + + if (newModelButton === toolBar.clicked(clickedOverlay)) { + toggleNewModelButton(); + return true; + } + + if (clickedOverlay === loadURLMenuItem) { + toggleNewModelButton(false); + url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]); + if (url !== null && url !== "") { + addModel(url); + } + return true; + } + + if (clickedOverlay === loadFileMenuItem) { + toggleNewModelButton(false); + + // TODO BUG: this is bug, if the user has never uploaded a model, this will throw an JS exception + file = Window.browse("Select your model file ...", + Settings.getValue("LastModelUploadLocation").path(), + "Model files (*.fst *.fbx)"); + //"Model files (*.fst *.fbx *.svo)"); + if (file !== null) { + Settings.setValue("LastModelUploadLocation", file); + modelUploader.upload(file, addModel); + } + return true; + } + + if (browseModelsButton === toolBar.clicked(clickedOverlay)) { + toggleNewModelButton(false); + url = Window.s3Browse(".*(fbx|FBX)"); + if (url !== null && url !== "") { + addModel(url); + } + return true; + } + + if (newCubeButton === toolBar.clicked(clickedOverlay)) { + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + + if (position.x > 0 && position.y > 0 && position.z > 0) { + Entities.addEntity({ + type: "Box", + position: position, + dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, + color: { red: 255, green: 0, blue: 0 } + + }); + } else { + print("Can't create box: Box would be out of bounds."); + } + return true; + } + + if (newSphereButton === toolBar.clicked(clickedOverlay)) { + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + + if (position.x > 0 && position.y > 0 && position.z > 0) { + Entities.addEntity({ + type: "Sphere", + position: position, + dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, + color: { red: 255, green: 0, blue: 0 } + }); + } else { + print("Can't create box: Box would be out of bounds."); + } + return true; + } + + + return false; + }; + + that.cleanup = function () { + toolBar.cleanup(); + Overlays.deleteOverlay(loadURLMenuItem); + Overlays.deleteOverlay(loadFileMenuItem); + }; + + return that; +}()); + + +var exportMenu = null; + +function isLocked(properties) { + // special case to lock the ground plane model in hq. + if (location.hostname == "hq.highfidelity.io" && + properties.modelURL == "https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/Terrain_Reduce_forAlpha.fbx") { + return true; + } + return false; +} + + +var entitySelected = false; +var selectedEntityID; +var selectedEntityProperties; +var mouseLastPosition; +var orientation; +var intersection; + + +var SCALE_FACTOR = 200.0; + +function rayPlaneIntersection(pickRay, point, normal) { + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / Vec3.dot(pickRay.direction, normal); + + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); +} + + +function mousePressEvent(event) { + mouseLastPosition = { x: event.x, y: event.y }; + entitySelected = false; + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + // Event handled; do nothing. + return; + } else { + // If we aren't active and didn't click on an overlay: quit + if (!isActive) { + return; + } + + var pickRay = Camera.computePickRay(event.x, event.y); + Vec3.print("[Mouse] Looking at: ", pickRay.origin); + var foundIntersection = Entities.findRayIntersection(pickRay); + + if(!foundIntersection.accurate) { + return; + } + var foundEntity = foundIntersection.entityID; + + if (!foundEntity.isKnownID) { + var identify = Entities.identifyEntity(foundEntity); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")"); + return; + } + foundEntity = identify; + } + + var properties = Entities.getEntityProperties(foundEntity); + if (isLocked(properties)) { + print("Model locked " + properties.id); + } else { + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal); + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + entitySelected = true; + selectedEntityID = foundEntity; + selectedEntityProperties = properties; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); + + print("Model selected selectedEntityID:" + selectedEntityID.id); + + } + } + } + if (entitySelected) { + selectedEntityProperties.oldDimensions = selectedEntityProperties.dimensions; + selectedEntityProperties.oldPosition = { + x: selectedEntityProperties.position.x, + y: selectedEntityProperties.position.y, + z: selectedEntityProperties.position.z, + }; + selectedEntityProperties.oldRotation = { + x: selectedEntityProperties.rotation.x, + y: selectedEntityProperties.rotation.y, + z: selectedEntityProperties.rotation.z, + w: selectedEntityProperties.rotation.w, + }; + selectedEntityProperties.glowLevel = 0.0; + + print("Clicked on " + selectedEntityID.id + " " + entitySelected); + tooltip.updateText(selectedEntityProperties); + tooltip.show(true); + selectionDisplay.select(selectedEntityID); + } +} + +var highlightedEntityID = { isKnownID: false }; + +function mouseMoveEvent(event) { + if (!isActive) { + return; + } + + var pickRay = Camera.computePickRay(event.x, event.y); + if (!entitySelected) { + var entityIntersection = Entities.findRayIntersection(pickRay); + if (entityIntersection.accurate) { + if(highlightedEntityID.isKnownID && highlightedEntityID.id != entityIntersection.entityID.id) { + selectionDisplay.unhighlightSelectable(highlightedEntityID); + highlightedEntityID = { id: -1, isKnownID: false }; + } + + var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), + entityIntersection.properties.position)) * 180 / 3.14; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (entityIntersection.entityID.isKnownID && sizeOK) { + if (wantEntityGlow) { + Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); + } + highlightedEntityID = entityIntersection.entityID; + selectionDisplay.highlightSelectable(entityIntersection.entityID); + } + + } + return; + } + + if (entitySelected) { + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityProperties.oldPosition, + Quat.getFront(orientation)); + var vector = Vec3.subtract(newIntersection, intersection) + selectedEntityProperties.position = Vec3.sum(selectedEntityProperties.oldPosition, vector); + + + Entities.editEntity(selectedEntityID, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + selectionDisplay.highlightSelectable(selectedEntityID, selectedEntityProperties); // TODO: this should be more than highlighted + } +} + + +function mouseReleaseEvent(event) { + if (!isActive) { + return; + } + if (entitySelected) { + tooltip.show(false); + } + + entitySelected = false; +} + +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + + +// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already +// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that +// added it. +var modelMenuAddedDelete = false; +function setupModelMenus() { + print("setupModelMenus()"); + // adj our menuitems + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...", + shortcutKeyEvent: { text: "`" }, afterItem: "Models" }); + if (!Menu.menuItemExists("Edit", "Delete")) { + print("no delete... adding ours"); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", + shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" }); + modelMenuAddedDelete = true; + } else { + print("delete exists... don't add ours"); + } + + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L", + afterItem: "Paste Models", isCheckable: true }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S", + afterItem: "Allow Select Large Models", isCheckable: true }); + + Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); + Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" }); + Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" }); +} + +setupModelMenus(); // do this when first running our script. + +function cleanupModelMenus() { + Menu.removeSeparator("Edit", "Models"); + Menu.removeMenuItem("Edit", "Edit Properties..."); + if (modelMenuAddedDelete) { + // delete our menuitems + Menu.removeMenuItem("Edit", "Delete"); + } + + Menu.removeMenuItem("Edit", "Paste Models"); + Menu.removeMenuItem("Edit", "Allow Select Large Models"); + Menu.removeMenuItem("Edit", "Allow Select Small Models"); + + Menu.removeSeparator("File", "Models"); + Menu.removeMenuItem("File", "Export Models"); + Menu.removeMenuItem("File", "Import Models"); +} + +Script.scriptEnding.connect(function() { + progressDialog.cleanup(); + toolBar.cleanup(); + cleanupModelMenus(); + tooltip.cleanup(); + modelImporter.cleanup(); + selectionDisplay.cleanup(); + if (exportMenu) { + exportMenu.close(); + } +}); + +// Do some stuff regularly, like check for placement of various overlays +Script.update.connect(function (deltaTime) { + toolBar.move(); + progressDialog.move(); +}); + +function handeMenuEvent(menuItem) { + if (menuItem == "Allow Select Small Models") { + allowSmallModels = Menu.isOptionChecked("Allow Select Small Models"); + } else if (menuItem == "Allow Select Large Models") { + allowLargeModels = Menu.isOptionChecked("Allow Select Large Models"); + } else if (menuItem == "Delete") { + if (entitySelected) { + print(" Delete Entity.... selectedEntityID="+ selectedEntityID); + Entities.deleteEntity(selectedEntityID); + selectionDisplay.unselect(selectedEntityID); + entitySelected = false; + } else { + print(" Delete Entity.... not holding..."); + } + } else if (menuItem == "Edit Properties...") { + // good place to put the properties dialog + } else if (menuItem == "Paste Models") { + modelImporter.paste(); + } else if (menuItem == "Export Models") { + if (!exportMenu) { + exportMenu = new ExportMenu({ + onClose: function () { + exportMenu = null; + } + }); + } + } else if (menuItem == "Import Models") { + modelImporter.doImport(); + } + tooltip.show(false); +} + +Menu.menuItemEvent.connect(handeMenuEvent); + +Controller.keyReleaseEvent.connect(function (event) { + // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items + if (event.text == "`") { + handeMenuEvent("Edit Properties..."); + } + if (event.text == "BACKSPACE") { + handeMenuEvent("Delete"); + } +}); + + + From 956c212db2fbfe6fce454534806533cb8b787856 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 15:24:23 -0700 Subject: [PATCH 044/104] manage sets of potential connections in ice server --- ice-server/src/IceServer.cpp | 67 ++++++++++++++++++++---- ice-server/src/IceServer.h | 6 ++- libraries/networking/src/NetworkPeer.cpp | 9 ++++ libraries/networking/src/NetworkPeer.h | 2 + 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index be97df37a4..e3db679009 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -11,6 +11,7 @@ #include +#include #include #include @@ -22,7 +23,8 @@ const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; IceServer::IceServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _id(QUuid::createUuid()), - _serverSocket() + _serverSocket(), + _activePeers() { // start the ice-server socket qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT; @@ -84,24 +86,67 @@ void IceServer::processDatagrams() { QUuid connectRequestID; hearbeatStream >> connectRequestID; + // get the peers asking for connections with this peer + QSet& requestingConnections = _currentConnections[senderUUID]; + if (!connectRequestID.isNull()) { qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestID); - // check if we have that ID - if we do we can respond with their info, otherwise nothing we can do - SharedNetworkPeer matchingConectee = _activePeers.value(connectRequestID); - if (matchingConectee) { - QByteArray heartbeatResponse = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeatResponse, _id); - QDataStream responseStream(&heartbeatResponse, QIODevice::Append); - - responseStream << matchingConectee; - - _serverSocket.writeDatagram(heartbeatResponse, sendingSockAddr.getAddress(), sendingSockAddr.getPort()); - } + // ensure this peer is in the set of current connections for the peer with ID it wants to connect with + _currentConnections[connectRequestID].insert(senderUUID); + + // add the ID of the node they have said they would like to connect to + requestingConnections.insert(connectRequestID); + } + + if (requestingConnections.size() > 0) { + // send a heartbeart response based on the set of connections + qDebug() << "Sending a heartbeat response to" << senderUUID << "who has" << requestingConnections.size() + << "potential connections"; + sendHeartbeatResponse(sendingSockAddr, requestingConnections); } } } } +void IceServer::sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet& connections) { + QSet::iterator peerID = connections.begin(); + + QByteArray outgoingPacket(MAX_PACKET_SIZE, 0); + int currentPacketSize = populatePacketHeader(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id); + + // go through the connections, sending packets containing connection information for those nodes + while (peerID != connections.end()) { + SharedNetworkPeer matchingPeer = _activePeers.value(*peerID); + // if this node is inactive we remove it from the set + if (!matchingPeer) { + peerID = connections.erase(peerID); + } else { + // get the byte array for this peer + QByteArray peerBytes = matchingPeer->toByteArray(); + + if (currentPacketSize + peerBytes.size() > MAX_PACKET_SIZE) { + // write the current packet + _serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize, + destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + + // reset the packet size to our number of header bytes + currentPacketSize = populatePacketHeader(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id); + } + + // append the current peer bytes + outgoingPacket.insert(currentPacketSize, peerBytes); + currentPacketSize += peerBytes.size(); + + ++peerID; + } + } + + // write the last packet + _serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize, + destinationSockAddr.getAddress(), destinationSockAddr.getPort()); +} + void IceServer::clearInactivePeers() { NetworkPeerHash::iterator peerItem = _activePeers.begin(); diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 9c4c4242dc..e15bda1211 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -27,9 +27,13 @@ private slots: void processDatagrams(); void clearInactivePeers(); private: + + void sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet& connections); + QUuid _id; - NetworkPeerHash _activePeers; QUdpSocket _serverSocket; + NetworkPeerHash _activePeers; + QHash > _currentConnections; }; #endif // hifi_IceServer_h \ No newline at end of file diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 1da6836bf9..79bed4f226 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -70,6 +70,15 @@ void NetworkPeer::activateSymmetricSocket() { _activeSocket = &_symmetricSocket; } +QByteArray NetworkPeer::toByteArray() const { + QByteArray peerByteArray; + + QDataStream peerStream(&peerByteArray, QIODevice::Append); + peerStream << *this; + + return peerByteArray; +} + QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer) { out << peer._uuid; out << peer._publicSocket; diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index dd32666b62..18015bc605 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -47,6 +47,8 @@ public: quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } + QByteArray toByteArray() const; + friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer); friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer); protected: From c36774e85da24f3a280d21c7ff582a7f2e20f288 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 16:05:15 -0700 Subject: [PATCH 045/104] handle ice response packet in domain handler --- libraries/networking/src/DomainHandler.cpp | 22 ++++++++ libraries/networking/src/DomainHandler.h | 5 ++ libraries/networking/src/NetworkPeer.cpp | 66 ++++++++++------------ libraries/networking/src/NetworkPeer.h | 23 ++++---- libraries/networking/src/Node.cpp | 46 +++++++++++++++ libraries/networking/src/Node.h | 13 +++++ libraries/networking/src/NodeList.cpp | 2 +- 7 files changed, 127 insertions(+), 50 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index e5c8abb171..2eb11f4922 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -26,6 +26,7 @@ DomainHandler::DomainHandler(QObject* parent) : _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _assignmentUUID(), _iceServerSockAddr(), + _icePeer(), _isConnected(false), _handshakeTimer(NULL), _settingsObject(), @@ -36,8 +37,12 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); + _iceServerSockAddr = HifiSockAddr(); + _icePeer = NetworkPeer(); + _isConnected = false; + emit disconnectedFromDomain(); if (_handshakeTimer) { @@ -231,3 +236,20 @@ void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirement // initializeDTLSSession(); } + +void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { + QDataStream iceResponseStream(icePacket); + iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket)); + + NetworkPeer packetPeer; + iceResponseStream >> packetPeer; + + if (packetPeer.getUUID() != _uuid) { + qDebug() << "Received a network peer with ID that does not match current domain. Will not attempt connection."; + } else { + qDebug() << "Received network peer object for domain -" << packetPeer; + _icePeer = packetPeer; + + emit requestICEConnectionAttempt(); + } +} diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index ffd288d93d..4c58a2b615 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -20,6 +20,7 @@ #include #include "HifiSockAddr.h" +#include "NetworkPeer.h" const QString DEFAULT_DOMAIN_HOSTNAME = "sandbox.highfidelity.io"; @@ -55,6 +56,7 @@ public: void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } bool requiresICE() const { return !_iceServerSockAddr.isNull(); } + NetworkPeer& getICEPeer() { return _icePeer; } bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); @@ -64,6 +66,7 @@ public: const QJsonObject& getSettingsObject() const { return _settingsObject; } void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket); + void processICEResponsePacket(const QByteArray& icePacket); void softReset(); public slots: @@ -77,6 +80,7 @@ signals: void hostnameChanged(const QString& hostname); void connectedToDomain(const QString& hostname); void disconnectedFromDomain(); + void requestICEConnectionAttempt(); void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); @@ -89,6 +93,7 @@ private: HifiSockAddr _sockAddr; QUuid _assignmentUUID; HifiSockAddr _iceServerSockAddr; + NetworkPeer _icePeer; bool _isConnected; QTimer* _handshakeTimer; QJsonObject _settingsObject; diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 79bed4f226..1dda9eb7b1 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -16,58 +16,50 @@ #include "NetworkPeer.h" -NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : - _uuid(uuid), - _publicSocket(publicSocket), - _localSocket(localSocket), - _symmetricSocket(), - _activeSocket(NULL), +NetworkPeer::NetworkPeer() : + _uuid(), + _publicSocket(), + _localSocket(), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _lastHeardMicrostamp(usecTimestampNow()) { } -void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { - if (_activeSocket == &_publicSocket) { - // if the active socket was the public socket then reset it to NULL - _activeSocket = NULL; - } +NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : + _uuid(uuid), + _publicSocket(publicSocket), + _localSocket(localSocket), + _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), + _lastHeardMicrostamp(usecTimestampNow()) +{ - _publicSocket = publicSocket; } -void NetworkPeer::setLocalSocket(const HifiSockAddr& localSocket) { - if (_activeSocket == &_localSocket) { - // if the active socket was the local socket then reset it to NULL - _activeSocket = NULL; - } +NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) { - _localSocket = localSocket; -} - -void NetworkPeer::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { - if (_activeSocket == &_symmetricSocket) { - // if the active socket was the symmetric socket then reset it to NULL - _activeSocket = NULL; - } + _uuid = otherPeer._uuid; + _publicSocket = otherPeer._publicSocket; + _localSocket = otherPeer._localSocket; - _symmetricSocket = symmetricSocket; + _wakeTimestamp = otherPeer._wakeTimestamp; + _lastHeardMicrostamp = otherPeer._lastHeardMicrostamp; } -void NetworkPeer::activateLocalSocket() { - qDebug() << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_localSocket; +NetworkPeer& NetworkPeer::operator=(const NetworkPeer& otherPeer) { + NetworkPeer temp(otherPeer); + swap(temp); + return *this; } -void NetworkPeer::activatePublicSocket() { - qDebug() << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_publicSocket; -} - -void NetworkPeer::activateSymmetricSocket() { - qDebug() << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_symmetricSocket; +void NetworkPeer::swap(NetworkPeer& otherPeer) { + using std::swap; + + swap(_uuid, otherPeer._uuid); + swap(_publicSocket, otherPeer._publicSocket); + swap(_localSocket, otherPeer._localSocket); + swap(_wakeTimestamp, otherPeer._wakeTimestamp); + swap(_lastHeardMicrostamp, otherPeer._lastHeardMicrostamp); } QByteArray NetworkPeer::toByteArray() const { diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 18015bc605..c590c49cf3 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -23,23 +23,22 @@ const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000; class NetworkPeer : public QObject { public: + NetworkPeer(); NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); + // privatize copy and assignment operator to disallow peer copying + NetworkPeer(const NetworkPeer &otherPeer); + NetworkPeer& operator=(const NetworkPeer& otherPeer); + const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } + void reset(); + const HifiSockAddr& getPublicSocket() const { return _publicSocket; } - void setPublicSocket(const HifiSockAddr& publicSocket); + virtual void setPublicSocket(const HifiSockAddr& publicSocket) { _publicSocket = publicSocket; } const HifiSockAddr& getLocalSocket() const { return _localSocket; } - void setLocalSocket(const HifiSockAddr& localSocket); - const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } - void setSymmetricSocket(const HifiSockAddr& symmetricSocket); - - const HifiSockAddr* getActiveSocket() const { return _activeSocket; } - - void activatePublicSocket(); - void activateLocalSocket(); - void activateSymmetricSocket(); + virtual void setLocalSocket(const HifiSockAddr& localSocket) { _localSocket = localSocket; } quint64 getWakeTimestamp() const { return _wakeTimestamp; } void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } @@ -56,11 +55,11 @@ protected: HifiSockAddr _publicSocket; HifiSockAddr _localSocket; - HifiSockAddr _symmetricSocket; - HifiSockAddr* _activeSocket; quint64 _wakeTimestamp; quint64 _lastHeardMicrostamp; +private: + void swap(NetworkPeer& otherPeer); }; QDebug operator<<(QDebug debug, const NetworkPeer &peer); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index a25350c3f3..20461cb754 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "Node.h" #include "SharedUtil.h" @@ -45,6 +47,8 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : NetworkPeer(uuid, publicSocket, localSocket), _type(type), + _activeSocket(NULL), + _symmetricSocket(), _connectionSecret(), _bytesReceivedMovingAverage(NULL), _linkedData(NULL), @@ -90,6 +94,48 @@ void Node::updateClockSkewUsec(int clockSkewSample) { _clockSkewUsec = (int)_clockSkewMovingPercentile.getValueAtPercentile(); } +void Node::setPublicSocket(const HifiSockAddr& publicSocket) { + if (_activeSocket == &_publicSocket) { + // if the active socket was the public socket then reset it to NULL + _activeSocket = NULL; + } + + _publicSocket = publicSocket; +} + +void Node::setLocalSocket(const HifiSockAddr& localSocket) { + if (_activeSocket == &_localSocket) { + // if the active socket was the local socket then reset it to NULL + _activeSocket = NULL; + } + + _localSocket = localSocket; +} + +void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { + if (_activeSocket == &_symmetricSocket) { + // if the active socket was the symmetric socket then reset it to NULL + _activeSocket = NULL; + } + + _symmetricSocket = symmetricSocket; +} + +void Node::activateLocalSocket() { + qDebug() << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + _activeSocket = &_localSocket; +} + +void Node::activatePublicSocket() { + qDebug() << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + _activeSocket = &_publicSocket; +} + +void Node::activateSymmetricSocket() { + qDebug() << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + _activeSocket = &_symmetricSocket; +} + QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; out << node._uuid; diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 238331947c..994ddd3bec 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -77,6 +77,17 @@ public: void updateClockSkewUsec(int clockSkewSample); QMutex& getMutex() { return _mutex; } + virtual void setPublicSocket(const HifiSockAddr& publicSocket); + virtual void setLocalSocket(const HifiSockAddr& localSocket); + const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } + virtual void setSymmetricSocket(const HifiSockAddr& symmetricSocket); + + const HifiSockAddr* getActiveSocket() const { return _activeSocket; } + + void activatePublicSocket(); + void activateLocalSocket(); + void activateSymmetricSocket(); + friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); @@ -87,6 +98,8 @@ private: NodeType_t _type; + HifiSockAddr* _activeSocket; + HifiSockAddr _symmetricSocket; QUuid _connectionSecret; SimpleMovingAverage* _bytesReceivedMovingAverage; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index a1b6011032..0333b4fb34 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -124,7 +124,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr break; } case PacketTypeIceServerHeartbeatResponse: { - + _domainHandler.processICEResponsePacket(packet); break; } case PacketTypePing: { From c7aaf0ce4ac6d117bd98a607483f4368c72720bc Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 16:24:46 -0700 Subject: [PATCH 046/104] handle ping out from node requiring ice connection to domain --- domain-server/src/DomainServer.cpp | 6 +++ libraries/networking/src/DomainHandler.cpp | 2 +- libraries/networking/src/DomainHandler.h | 3 +- libraries/networking/src/LimitedNodeList.cpp | 34 ++++++++++++- libraries/networking/src/LimitedNodeList.h | 14 +++++- libraries/networking/src/NetworkPeer.h | 2 + libraries/networking/src/NodeList.cpp | 53 ++++++++------------ libraries/networking/src/NodeList.h | 13 +---- 8 files changed, 77 insertions(+), 50 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 78baf803fa..28765e0620 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1028,6 +1028,12 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS case PacketTypeStunResponse: nodeList->processSTUNResponse(receivedPacket); break; + case PacketTypePing: { + QByteArray pingReplyPacket = nodeList->constructPingReplyPacket(receivedPacket); + nodeList->writeUnverifiedDatagram(pingReplyPacket, senderSockAddr); + + break; + } default: break; } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 2eb11f4922..d4da00db2c 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -135,7 +135,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, // re-set the domain info to connect to new domain hardReset(); - _uuid = id; + setUUID(id); _iceServerSockAddr = HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); qDebug() << "Domain ID changed to" << uuidStringWithoutCurlyBraces(_uuid) diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 4c58a2b615..f490fc5e65 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -56,7 +56,8 @@ public: void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } bool requiresICE() const { return !_iceServerSockAddr.isNull(); } - NetworkPeer& getICEPeer() { return _icePeer; } + const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; } + const NetworkPeer& getICEPeer() const { return _icePeer; } bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index b32fbe3b37..4d603e6929 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -459,6 +459,35 @@ unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeS return n; } +QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType) { + QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); + + QDataStream packetStream(&pingPacket, QIODevice::Append); + + packetStream << pingType; + packetStream << usecTimestampNow(); + + return pingPacket; +} + +QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket) { + QDataStream pingPacketStream(pingPacket); + pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); + + PingType_t typeFromOriginalPing; + pingPacketStream >> typeFromOriginalPing; + + quint64 timeFromOriginalPing; + pingPacketStream >> timeFromOriginalPing; + + QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); + QDataStream packetStream(&replyPacket, QIODevice::Append); + + packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); + + return replyPacket; +} + SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) { if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { @@ -619,7 +648,8 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { return false; } -void LimitedNodeList::sendHeartbeatToIceServer(QUuid headerID, const QUuid& connectionRequestID) { +void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr, + QUuid headerID, const QUuid& connectionRequestID) { if (headerID.isNull()) { headerID = _sessionUUID; @@ -637,5 +667,5 @@ void LimitedNodeList::sendHeartbeatToIceServer(QUuid headerID, const QUuid& conn << uuidStringWithoutCurlyBraces(connectionRequestID); } - _nodeSocket.writeDatagram(iceRequestByteArray, QHostAddress::LocalHost, ICE_SERVER_DEFAULT_PORT); + writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr); } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 1e97ba8190..6f6c4539b8 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -52,6 +52,14 @@ typedef QSharedPointer SharedNodePointer; typedef QHash NodeHash; Q_DECLARE_METATYPE(SharedNodePointer) +typedef quint8 PingType_t; +namespace PingType { + const PingType_t Agnostic = 0; + const PingType_t Local = 1; + const PingType_t Public = 2; + const PingType_t Symmetric = 3; +} + class LimitedNodeList : public QObject { Q_OBJECT public: @@ -104,10 +112,14 @@ public: void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); + QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); + QByteArray constructPingReplyPacket(const QByteArray& pingPacket); + virtual void sendSTUNRequest(); virtual bool processSTUNResponse(const QByteArray& packet); - void sendHeartbeatToIceServer(QUuid headerID = QUuid(), const QUuid& connectRequestID = QUuid()); + void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr, + QUuid headerID = QUuid(), const QUuid& connectRequestID = QUuid()); public slots: void reset(); void eraseAllNodes(); diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index c590c49cf3..1e9b61d9f2 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -30,6 +30,8 @@ public: NetworkPeer(const NetworkPeer &otherPeer); NetworkPeer& operator=(const NetworkPeer& otherPeer); + bool isNull() const { return _uuid.isNull(); } + const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 0333b4fb34..25c86a41b8 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -64,6 +64,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // clear our NodeList when the domain changes connect(&_domainHandler, &DomainHandler::hostnameChanged, this, &NodeList::reset); + // handle ICE signal from DS so connection is attempted immediately + connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer); + // clear our NodeList when logout is requested connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); } @@ -158,6 +161,8 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr // set the ping time for this node for stat collection timePingReply(packet, sendingNode); + } else if (uuidFromPacketHeader(packet) == _domainHandler.getUUID()) { + qDebug() << "RECEIVED A REPLY FROM DOMAIN"; } break; @@ -247,7 +252,7 @@ void NodeList::sendDomainServerCheckIn() { // send a STUN request to figure it out sendSTUNRequest(); } else if (!_domainHandler.isConnected() && _domainHandler.requiresICE()) { - sendICERequestForDomainConnection(); + handleICEConnectionToDomainServer(); } else if (!_domainHandler.getIP().isNull()) { bool isUsingDTLS = false; @@ -304,11 +309,22 @@ void NodeList::sendDomainServerCheckIn() { } } -void NodeList::sendICERequestForDomainConnection() { - +void NodeList::handleICEConnectionToDomainServer() { static QUuid iceUUID = QUuid::createUuid(); - LimitedNodeList::sendHeartbeatToIceServer(iceUUID, _domainHandler.getUUID()); + if (_domainHandler.getICEPeer().isNull()) { + LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), iceUUID, _domainHandler.getUUID()); + } else { + qDebug() << "Sending ping packets to establish connectivity with domain-server with ID" + << uuidStringWithoutCurlyBraces(_domainHandler.getUUID()); + + // send the ping packet to the local and public sockets for this node + QByteArray localPingPacket = constructPingPacket(PingType::Local); + writeDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket(), iceUUID); + + QByteArray publicPingPacket = constructPingPacket(PingType::Public); + writeDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket(), iceUUID); + } } int NodeList::processDomainServerList(const QByteArray& packet) { @@ -382,35 +398,6 @@ void NodeList::sendAssignment(Assignment& assignment) { _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); } -QByteArray NodeList::constructPingPacket(PingType_t pingType) { - QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); - - QDataStream packetStream(&pingPacket, QIODevice::Append); - - packetStream << pingType; - packetStream << usecTimestampNow(); - - return pingPacket; -} - -QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { - QDataStream pingPacketStream(pingPacket); - pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); - - PingType_t typeFromOriginalPing; - pingPacketStream >> typeFromOriginalPing; - - quint64 timeFromOriginalPing; - pingPacketStream >> timeFromOriginalPing; - - QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); - QDataStream packetStream(&replyPacket, QIODevice::Append); - - packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); - - return replyPacket; -} - void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { // send the ping packet to the local and public sockets for this node diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index ad0f74e517..c1e2d12319 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -37,14 +37,6 @@ const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; class Assignment; -typedef quint8 PingType_t; -namespace PingType { - const PingType_t Agnostic = 0; - const PingType_t Local = 1; - const PingType_t Public = 2; - const PingType_t Symmetric = 3; -} - class NodeList : public LimitedNodeList { Q_OBJECT public: @@ -69,9 +61,6 @@ public: void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); - - QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); - QByteArray constructPingReplyPacket(const QByteArray& pingPacket); void pingPunchForInactiveNode(const SharedNodePointer& node); @@ -91,7 +80,7 @@ private: void sendSTUNRequest(); bool processSTUNResponse(const QByteArray& packet); - void sendICERequestForDomainConnection(); + void handleICEConnectionToDomainServer(); void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); From 63877b0756b0ff0f160e36ec966ad42b3eff45a5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 16:51:12 -0700 Subject: [PATCH 047/104] handle domain connection via ICE from NodeList --- domain-server/src/DomainServer.cpp | 6 ++++-- libraries/networking/src/DomainHandler.cpp | 13 ++++++++++-- libraries/networking/src/DomainHandler.h | 2 ++ libraries/networking/src/LimitedNodeList.cpp | 9 +++++--- libraries/networking/src/LimitedNodeList.h | 2 +- libraries/networking/src/NodeList.cpp | 22 +++++++++++++++----- libraries/networking/src/PacketHeaders.h | 7 +++++-- 7 files changed, 46 insertions(+), 15 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 28765e0620..1bbe61dfcb 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -983,8 +983,10 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, domainUpdateJSON.toUtf8()); } +const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr(QHostAddress::LocalHost, ICE_SERVER_DEFAULT_PORT); + void DomainServer::sendHearbeatToIceServer() { - LimitedNodeList::getInstance()->sendHeartbeatToIceServer(); + LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR); } void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { @@ -1028,7 +1030,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS case PacketTypeStunResponse: nodeList->processSTUNResponse(receivedPacket); break; - case PacketTypePing: { + case PacketTypeUnverifiedPing: { QByteArray pingReplyPacket = nodeList->constructPingReplyPacket(receivedPacket); nodeList->writeUnverifiedDatagram(pingReplyPacket, senderSockAddr); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index d4da00db2c..b438d9bdf4 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -138,11 +138,20 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, setUUID(id); _iceServerSockAddr = HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); - qDebug() << "Domain ID changed to" << uuidStringWithoutCurlyBraces(_uuid) - << "- ICE required via ice server at" << iceServerHostname; + qDebug() << "ICE required to connect to domain via ice server at" << iceServerHostname; } } +void DomainHandler::activateICELocalSocket() { + _sockAddr = _icePeer.getLocalSocket(); + _hostname = _sockAddr.getAddress().toString(); +} + +void DomainHandler::activateICEPublicSocket() { + _sockAddr = _icePeer.getPublicSocket(); + _hostname = _sockAddr.getAddress().toString(); +} + void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { for (int i = 0; i < hostInfo.addresses().size(); i++) { if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index f490fc5e65..9215bd004d 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -58,6 +58,8 @@ public: bool requiresICE() const { return !_iceServerSockAddr.isNull(); } const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; } const NetworkPeer& getICEPeer() const { return _icePeer; } + void activateICELocalSocket(); + void activateICEPublicSocket(); bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 4d603e6929..ddcfcd7d08 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -459,8 +459,8 @@ unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeS return n; } -QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType) { - QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); +QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType, bool isVerified) { + QByteArray pingPacket = byteArrayWithPopulatedHeader(isVerified ? PacketTypePing : PacketTypeUnverifiedPing); QDataStream packetStream(&pingPacket, QIODevice::Append); @@ -480,7 +480,10 @@ QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacke quint64 timeFromOriginalPing; pingPacketStream >> timeFromOriginalPing; - QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); + PacketType replyType = (packetTypeForPacket(pingPacket) == PacketTypePing) + ? PacketTypePingReply : PacketTypeUnverifiedPingReply; + + QByteArray replyPacket = byteArrayWithPopulatedHeader(replyType); QDataStream packetStream(&replyPacket, QIODevice::Append); packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 6f6c4539b8..dfba7f6c72 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -112,7 +112,7 @@ public: void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); - QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); + QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic, bool isVerified = true); QByteArray constructPingReplyPacket(const QByteArray& pingPacket); virtual void sendSTUNRequest(); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 25c86a41b8..846b5395a8 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -161,12 +161,24 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr // set the ping time for this node for stat collection timePingReply(packet, sendingNode); - } else if (uuidFromPacketHeader(packet) == _domainHandler.getUUID()) { - qDebug() << "RECEIVED A REPLY FROM DOMAIN"; } break; } + case PacketTypeUnverifiedPingReply: { + qDebug() << "Received reply from domain-server on" << senderSockAddr; + + // for now we're unsafely assuming this came back from the domain + if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) { + qDebug() << "Connecting to domain using local socket"; + _domainHandler.activateICELocalSocket(); + } else if (senderSockAddr == _domainHandler.getICEPeer().getPublicSocket()) { + qDebug() << "Conecting to domain using public socket"; + _domainHandler.activateICEPublicSocket(); + } else { + qDebug() << "Reply does not match either local or public socket for domain. Will not connect."; + } + } case PacketTypeStunResponse: { // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch // pass it along so it can be processed into our public address and port @@ -251,7 +263,7 @@ void NodeList::sendDomainServerCheckIn() { // we don't know our public socket and we need to send it to the domain server // send a STUN request to figure it out sendSTUNRequest(); - } else if (!_domainHandler.isConnected() && _domainHandler.requiresICE()) { + } else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { handleICEConnectionToDomainServer(); } else if (!_domainHandler.getIP().isNull()) { @@ -319,10 +331,10 @@ void NodeList::handleICEConnectionToDomainServer() { << uuidStringWithoutCurlyBraces(_domainHandler.getUUID()); // send the ping packet to the local and public sockets for this node - QByteArray localPingPacket = constructPingPacket(PingType::Local); + QByteArray localPingPacket = constructPingPacket(PingType::Local, false); writeDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket(), iceUUID); - QByteArray publicPingPacket = constructPingPacket(PingType::Public); + QByteArray publicPingPacket = constructPingPacket(PingType::Public, false); writeDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket(), iceUUID); } } diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index dd1707521c..2d298d253d 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -72,7 +72,9 @@ enum PacketType { PacketTypeEntityEditNack, // 48 PacketTypeSignedTransactionPayment, PacketTypeIceServerHeartbeat, - PacketTypeIceServerHeartbeatResponse + PacketTypeIceServerHeartbeatResponse, + PacketTypeUnverifiedPing, + PacketTypeUnverifiedPingReply }; typedef char PacketVersion; @@ -83,7 +85,8 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery << PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack - << PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse; + << PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse + << PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply; const int NUM_BYTES_MD5_HASH = 16; const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; From 0d4ef4aaca44c61e2b6b76ad00c809b90fbc139b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 17:18:05 -0700 Subject: [PATCH 048/104] ping connecting ICE peers from domain-server --- domain-server/src/DomainServer.cpp | 46 ++++++++++++++++++++++++++- domain-server/src/DomainServer.h | 6 ++++ libraries/networking/src/NodeList.cpp | 6 ++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 1bbe61dfcb..c0daa94c77 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -354,7 +354,7 @@ void DomainServer::setupAutomaticNetworking() { // setup a timer to heartbeat with the ice-server every so often QTimer* iceHeartbeatTimer = new QTimer(this); - connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHearbeatToIceServer); + connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates); iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); // call our sendHeartbeaToIceServer immediately anytime a public address changes @@ -985,10 +985,51 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr(QHostAddress::LocalHost, ICE_SERVER_DEFAULT_PORT); +void DomainServer::performICEUpdates() { + sendHearbeatToIceServer(); + sendICEPingPackets(); +} + void DomainServer::sendHearbeatToIceServer() { LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR); } +void DomainServer::sendICEPingPackets() { + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + foreach(const NetworkPeer& peer, _connectingICEPeers) { + // send ping packets to this peer's interfaces + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" + << peer.getUUID(); + + // send the ping packet to the local and public sockets for this node + QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false); + nodeList->writeUnverifiedDatagram(localPingPacket, peer.getLocalSocket()); + + QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false); + nodeList->writeUnverifiedDatagram(publicPingPacket, peer.getPublicSocket()); + } +} + +void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { + // loop through the packet and pull out network peers + // any peer we don't have we add to the hash, otherwise we update + QDataStream iceResponseStream(packet); + iceResponseStream.skipRawData(numBytesForPacketHeader(packet)); + + NetworkPeer receivedPeer; + + while (!iceResponseStream.atEnd()) { + iceResponseStream >> receivedPeer; + + if (!_connectingICEPeers.contains(receivedPeer.getUUID())) { + qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; + } + + _connectingICEPeers[receivedPeer.getUUID()] = receivedPeer; + } +} + void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); @@ -1036,6 +1077,9 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS break; } + case PacketTypeIceServerHeartbeatResponse: + processICEHeartbeatResponse(receivedPacket); + break; default: break; } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index d5e38acf89..88da561f63 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -64,7 +64,9 @@ private slots: void requestCurrentPublicSocketViaSTUN(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); + void performICEUpdates(); void sendHearbeatToIceServer(); + void sendICEPingPackets(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); @@ -74,6 +76,7 @@ private: void setupAutomaticNetworking(); void updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress = QString()); + void processICEHeartbeatResponse(const QByteArray& packet); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); @@ -135,6 +138,9 @@ private: HifiSockAddr _localSockAddr; + QHash _connectingICEPeers; + QHash _connectedICEPeers; + DomainServerSettingsManager _settingsManager; }; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 846b5395a8..395a3d01c9 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -165,6 +165,12 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr break; } + case PacketTypeUnverifiedPing: { + // send back a reply + QByteArray replyPacket = constructPingReplyPacket(packet); + writeUnverifiedDatagram(replyPacket, senderSockAddr); + break; + } case PacketTypeUnverifiedPingReply: { qDebug() << "Received reply from domain-server on" << senderSockAddr; From 15cc08da9705193981d4f27c599358b9826b3940 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 17:21:07 -0700 Subject: [PATCH 049/104] initial handling of ping replies in domain-server --- domain-server/src/DomainServer.cpp | 8 ++++++++ domain-server/src/DomainServer.h | 1 + 2 files changed, 9 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c0daa94c77..b0fdb61ed5 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1030,6 +1030,10 @@ void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { } } +void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { + qDebug() << "looking for a node with ID" << uuidFromPacketHeader(packet) << "in connecting hash"; +} + void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); @@ -1077,6 +1081,10 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS break; } + case PacketTypeUnverifiedPingReply: { + processICEPingReply(receivedPacket, senderSockAddr); + break; + } case PacketTypeIceServerHeartbeatResponse: processICEHeartbeatResponse(receivedPacket); break; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 88da561f63..9f668981f0 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -76,6 +76,7 @@ private: void setupAutomaticNetworking(); void updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress = QString()); + void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr); void processICEHeartbeatResponse(const QByteArray& packet); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); From c67f421ea9b65a0c0c049d55041aac9a830805bd Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 2 Oct 2014 17:25:44 -0700 Subject: [PATCH 050/104] Generate additional vertices with different normals when normals differ by "crease" angle. --- interface/src/MetavoxelSystem.cpp | 124 ++++++++++++++++++++++++------ interface/src/MetavoxelSystem.h | 2 + 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index fe4d56c6fd..1e235b6116 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -964,6 +964,12 @@ void HeightfieldPreview::render(const glm::vec3& translation, float scale) const Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true, false); } +void VoxelPoint::setNormal(const glm::vec3& normal) { + this->normal[0] = (char)(normal.x * 127.0f); + this->normal[1] = (char)(normal.y * 127.0f); + this->normal[2] = (char)(normal.z * 127.0f); +} + VoxelBuffer::VoxelBuffer(const QVector& vertices, const QVector& indices, const QVector& materials) : _vertices(vertices), @@ -1535,6 +1541,14 @@ public: glm::vec3 normal; QRgb color; char material; + int axis; +}; + +class AxisIndex { +public: + int x, y, z; + + AxisIndex(int x = -1, int y = -1, int z = -1) : x(x), y(y), z(z) { } }; int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { @@ -1575,10 +1589,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { // as we scan down the cube generating vertices between grid points, we remember the indices of the last // (element, line, section--x, y, z) so that we can connect generated vertices as quads int expanded = size + 1; - QVector lineIndices(expanded, -1); - QVector lastLineIndices(expanded, -1); - QVector planeIndices(expanded * expanded, -1); - QVector lastPlaneIndices(expanded * expanded, -1); + QVector lineIndices(expanded); + QVector lastLineIndices(expanded); + QVector planeIndices(expanded * expanded); + QVector lastPlaneIndices(expanded * expanded); const int EDGES_PER_CUBE = 12; EdgeCrossing crossings[EDGES_PER_CUBE]; @@ -1589,7 +1603,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { for (int z = 0; z < expanded; z++) { const QRgb* colorY = colorZ; for (int y = 0; y < expanded; y++) { - int lastIndex = 0; + AxisIndex lastIndex; const QRgb* colorX = colorY; for (int x = 0; x < expanded; x++) { int alpha0 = colorX[0] >> ALPHA_OFFSET; @@ -1663,6 +1677,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[0]; } crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f, 0.0f); + crossing.axis = 0; } if (middleY) { if (alpha1 != alpha3) { @@ -1677,6 +1692,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[1]; } crossing.point = glm::vec3(1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f); + crossing.axis = 1; } if (alpha2 != alpha3) { QRgb hermite = hermiteBase[hermiteStride]; @@ -1690,6 +1706,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[size]; } crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f, 0.0f); + crossing.axis = 0; } if (middleZ) { if (alpha3 != alpha7) { @@ -1704,6 +1721,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[offset3]; } crossing.point = glm::vec3(1.0f, 1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); + crossing.axis = 2; } if (alpha5 != alpha7) { QRgb hermite = hermiteBase[hermiteArea + VoxelHermiteData::EDGE_COUNT + 1]; @@ -1717,6 +1735,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[offset5]; } crossing.point = glm::vec3(1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f); + crossing.axis = 1; } if (alpha6 != alpha7) { QRgb hermite = hermiteBase[hermiteArea + hermiteStride]; @@ -1730,6 +1749,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[offset6]; } crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f, 1.0f); + crossing.axis = 0; } } } @@ -1746,6 +1766,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[1]; } crossing.point = glm::vec3(1.0f, 0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); + crossing.axis = 2; } if (alpha4 != alpha5) { QRgb hermite = hermiteBase[hermiteArea]; @@ -1759,6 +1780,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[area]; } crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f, 1.0f); + crossing.axis = 0; } } } @@ -1775,6 +1797,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[0]; } crossing.point = glm::vec3(0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f); + crossing.axis = 1; } if (middleZ) { if (alpha2 != alpha6) { @@ -1789,6 +1812,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[size]; } crossing.point = glm::vec3(0.0f, 1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); + crossing.axis = 2; } if (alpha4 != alpha6) { QRgb hermite = hermiteBase[hermiteArea + 1]; @@ -1802,6 +1826,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[area]; } crossing.point = glm::vec3(0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f); + crossing.axis = 1; } } } @@ -1817,11 +1842,12 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { crossing.material = materialBase[0]; } crossing.point = glm::vec3(0.0f, 0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); + crossing.axis = 2; } // at present, we simply average the properties of each crossing as opposed to finding the vertex that // minimizes the quadratic error function as described in the reference paper glm::vec3 center; - glm::vec3 normal; + glm::vec3 axisNormals[3]; const int MAX_MATERIALS_PER_VERTEX = 4; quint8 materials[] = { 0, 0, 0, 0 }; glm::vec4 materialWeights; @@ -1830,7 +1856,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { for (int i = 0; i < crossingCount; i++) { const EdgeCrossing& crossing = crossings[i]; center += crossing.point; - normal += crossing.normal; + axisNormals[crossing.axis] += crossing.normal; red += qRed(crossing.color); green += qGreen(crossing.color); blue += qBlue(crossing.color); @@ -1853,16 +1879,18 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { } } } - normal = glm::normalize(normal); + glm::vec3 normal = glm::normalize(axisNormals[0] + axisNormals[1] + axisNormals[2]); center /= crossingCount; // use a sequence of Givens rotations to perform a QR decomposition // see http://www.cs.rice.edu/~jwarren/papers/techreport02408.pdf glm::mat4 r(0.0f); glm::vec4 bottom; + float smallestCosNormal = 1.0f; for (int i = 0; i < crossingCount; i++) { const EdgeCrossing& crossing = crossings[i]; bottom = glm::vec4(crossing.normal, glm::dot(crossing.normal, crossing.point - center)); + smallestCosNormal = qMin(smallestCosNormal, glm::dot(crossing.normal, normal)); for (int j = 0; j < 4; j++) { float angle = glm::atan(-bottom[j], r[j][j]); @@ -1931,17 +1959,69 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { { materials[0], materials[1], materials[2], materials[3] }, { (quint8)materialWeights[0], (quint8)materialWeights[1], (quint8)materialWeights[2], (quint8)materialWeights[3] } }; - int index = vertices.size(); - vertices.append(point); + + // determine whether we must "crease" by generating directional normals + const float CREASE_COS_NORMAL = glm::cos(glm::radians(40.0f)); + AxisIndex index(vertices.size(), vertices.size(), vertices.size()); + if (smallestCosNormal > CREASE_COS_NORMAL) { + vertices.append(point); + + } else { + axisNormals[0] = glm::normalize(axisNormals[0]); + axisNormals[1] = glm::normalize(axisNormals[1]); + axisNormals[2] = glm::normalize(axisNormals[2]); + glm::vec3 normalXY(glm::normalize(axisNormals[0] + axisNormals[1])); + glm::vec3 normalXZ(glm::normalize(axisNormals[0] + axisNormals[2])); + glm::vec3 normalYZ(glm::normalize(axisNormals[1] + axisNormals[2])); + if (glm::dot(axisNormals[0], normalXY) > CREASE_COS_NORMAL && + glm::dot(axisNormals[1], normalXY) > CREASE_COS_NORMAL) { + point.setNormal(normalXY); + vertices.append(point); + + point.setNormal(axisNormals[2]); + index.z = vertices.size(); + vertices.append(point); + + } else if (glm::dot(axisNormals[0], normalXZ) > CREASE_COS_NORMAL && + glm::dot(axisNormals[2], normalXZ) > CREASE_COS_NORMAL) { + point.setNormal(normalXZ); + vertices.append(point); + + point.setNormal(axisNormals[1]); + index.y = vertices.size(); + vertices.append(point); + + } else if (glm::dot(axisNormals[1], normalYZ) > CREASE_COS_NORMAL && + glm::dot(axisNormals[2], normalYZ) > CREASE_COS_NORMAL) { + point.setNormal(normalYZ); + vertices.append(point); + + point.setNormal(axisNormals[0]); + index.x = vertices.size(); + vertices.append(point); + + } else { + point.setNormal(axisNormals[0]); + vertices.append(point); + + point.setNormal(axisNormals[1]); + index.y = vertices.size(); + vertices.append(point); + + point.setNormal(axisNormals[2]); + index.z = vertices.size(); + vertices.append(point); + } + } // the first x, y, and z are repeated for the boundary edge; past that, we consider generating // quads for each edge that includes a transition, using indices of previously generated vertices if (x != 0 && y != 0 && z != 0) { if (alpha0 != alpha1) { - indices.append(index); - int index1 = lastLineIndices.at(x); - int index2 = lastPlaneIndices.at((y - 1) * expanded + x); - int index3 = lastPlaneIndices.at(y * expanded + x); + indices.append(index.x); + int index1 = lastLineIndices.at(x).x; + int index2 = lastPlaneIndices.at((y - 1) * expanded + x).x; + int index3 = lastPlaneIndices.at(y * expanded + x).x; if (alpha0 == 0) { // quad faces negative x indices.append(index3); indices.append(index2); @@ -1954,10 +2034,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { } if (alpha0 != alpha2) { - indices.append(index); - int index1 = lastIndex; - int index2 = lastPlaneIndices.at(y * expanded + x - 1); - int index3 = lastPlaneIndices.at(y * expanded + x); + indices.append(index.y); + int index1 = lastIndex.y; + int index2 = lastPlaneIndices.at(y * expanded + x - 1).y; + int index3 = lastPlaneIndices.at(y * expanded + x).y; if (alpha0 == 0) { // quad faces negative y indices.append(index1); indices.append(index2); @@ -1970,10 +2050,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { } if (alpha0 != alpha4) { - indices.append(index); - int index1 = lastIndex; - int index2 = lastLineIndices.at(x - 1); - int index3 = lastLineIndices.at(x); + indices.append(index.z); + int index1 = lastIndex.z; + int index2 = lastLineIndices.at(x - 1).z; + int index3 = lastLineIndices.at(x).z; if (alpha0 == 0) { // quad faces negative z indices.append(index3); indices.append(index2); diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 301e12473c..10110a5c5c 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -226,6 +226,8 @@ public: char normal[3]; quint8 materials[4]; quint8 materialWeights[4]; + + void setNormal(const glm::vec3& normal); }; /// Contains the information necessary to render a voxel block. From 5786d828a0fffef091057dc64fbd50722330b062 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 2 Oct 2014 17:30:48 -0700 Subject: [PATCH 051/104] pass ICE client ID through to domain-server for identification --- libraries/networking/src/DomainHandler.cpp | 4 ++++ libraries/networking/src/DomainHandler.h | 3 +++ libraries/networking/src/LimitedNodeList.cpp | 9 +++++---- libraries/networking/src/LimitedNodeList.h | 5 +++-- libraries/networking/src/NodeList.cpp | 16 ++++++++-------- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index b438d9bdf4..daa9b6eca6 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -25,6 +25,7 @@ DomainHandler::DomainHandler(QObject* parent) : _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _assignmentUUID(), + _iceClientID(), _iceServerSockAddr(), _icePeer(), _isConnected(false), @@ -138,6 +139,9 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, setUUID(id); _iceServerSockAddr = HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); + // refresh our ICE client UUID to something new + _iceClientID = QUuid::createUuid(); + qDebug() << "ICE required to connect to domain via ice server at" << iceServerHostname; } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 9215bd004d..b41bbd98aa 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -55,6 +55,8 @@ public: const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } + const QUuid& getICEClientID() const { return _iceClientID; } + bool requiresICE() const { return !_iceServerSockAddr.isNull(); } const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; } const NetworkPeer& getICEPeer() const { return _icePeer; } @@ -95,6 +97,7 @@ private: QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; + QUuid _iceClientID; HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; bool _isConnected; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index ddcfcd7d08..507788009a 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -459,8 +459,9 @@ unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeS return n; } -QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType, bool isVerified) { - QByteArray pingPacket = byteArrayWithPopulatedHeader(isVerified ? PacketTypePing : PacketTypeUnverifiedPing); +QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType, bool isVerified, const QUuid& packetHeaderID) { + QByteArray pingPacket = byteArrayWithPopulatedHeader(isVerified ? PacketTypePing : PacketTypeUnverifiedPing, + packetHeaderID); QDataStream packetStream(&pingPacket, QIODevice::Append); @@ -470,7 +471,7 @@ QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType, bool isVeri return pingPacket; } -QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket) { +QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID) { QDataStream pingPacketStream(pingPacket); pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); @@ -483,7 +484,7 @@ QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacke PacketType replyType = (packetTypeForPacket(pingPacket) == PacketTypePing) ? PacketTypePingReply : PacketTypeUnverifiedPingReply; - QByteArray replyPacket = byteArrayWithPopulatedHeader(replyType); + QByteArray replyPacket = byteArrayWithPopulatedHeader(replyType, packetHeaderID); QDataStream packetStream(&replyPacket, QIODevice::Append); packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index dfba7f6c72..a7ffc7ec28 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -112,8 +112,9 @@ public: void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); - QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic, bool isVerified = true); - QByteArray constructPingReplyPacket(const QByteArray& pingPacket); + QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic, bool isVerified = true, + const QUuid& packetHeaderID = QUuid()); + QByteArray constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID = QUuid()); virtual void sendSTUNRequest(); virtual bool processSTUNResponse(const QByteArray& packet); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 395a3d01c9..da11684ada 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -167,7 +167,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr } case PacketTypeUnverifiedPing: { // send back a reply - QByteArray replyPacket = constructPingReplyPacket(packet); + QByteArray replyPacket = constructPingReplyPacket(packet, _domainHandler.getICEClientID()); writeUnverifiedDatagram(replyPacket, senderSockAddr); break; } @@ -328,20 +328,20 @@ void NodeList::sendDomainServerCheckIn() { } void NodeList::handleICEConnectionToDomainServer() { - static QUuid iceUUID = QUuid::createUuid(); - if (_domainHandler.getICEPeer().isNull()) { - LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), iceUUID, _domainHandler.getUUID()); + LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), + _domainHandler.getICEClientID(), + _domainHandler.getUUID()); } else { qDebug() << "Sending ping packets to establish connectivity with domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getUUID()); // send the ping packet to the local and public sockets for this node - QByteArray localPingPacket = constructPingPacket(PingType::Local, false); - writeDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket(), iceUUID); + QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID()); + writeUnverifiedDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket()); - QByteArray publicPingPacket = constructPingPacket(PingType::Public, false); - writeDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket(), iceUUID); + QByteArray publicPingPacket = constructPingPacket(PingType::Public, false, _domainHandler.getICEClientID()); + writeUnverifiedDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket()); } } From aa634c8ff238731bb94bf97f3b6dfbb6fbebccb1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 17:55:32 -0700 Subject: [PATCH 052/104] added new methods to Vec3 and Quat --- libraries/script-engine/src/Quat.cpp | 4 ++++ libraries/script-engine/src/Quat.h | 1 + libraries/script-engine/src/Vec3.cpp | 4 ++++ libraries/script-engine/src/Vec3.h | 1 + 4 files changed, 10 insertions(+) diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index 5985858026..e961eb0acf 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -84,3 +84,7 @@ void Quat::print(const QString& lable, const glm::quat& q) { qDebug() << qPrintable(lable) << q.x << "," << q.y << "," << q.z << "," << q.w; } +bool Quat::equal(const glm::vec3& q1, const glm::vec3& q2) { + return q1 == q2; +} + diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index faae636f02..55bfa3c061 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -40,6 +40,7 @@ public slots: glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h); float dot(const glm::quat& q1, const glm::quat& q2); void print(const QString& lable, const glm::quat& q); + bool equal(const glm::vec3& q1, const glm::vec3& q2); }; #endif // hifi_Quat_h diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp index 7589ade3b6..689ce2c747 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -55,3 +55,7 @@ glm::vec3 Vec3::normalize(const glm::vec3& v) { void Vec3::print(const QString& lable, const glm::vec3& v) { qDebug() << qPrintable(lable) << v.x << "," << v.y << "," << v.z; } + +bool Vec3::equal(const glm::vec3& v1, const glm::vec3& v2) { + return v1 == v2; +} diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 598f9be432..2af1350e4a 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -36,6 +36,7 @@ public slots: float distance(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 normalize(const glm::vec3& v); void print(const QString& lable, const glm::vec3& v); + bool equal(const glm::vec3& v1, const glm::vec3& v2); }; From 729e40881d89604697a52d00ff6e2b6f80973be8 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 17:55:55 -0700 Subject: [PATCH 053/104] removed some old debug --- libraries/entities/src/EntityItem.cpp | 3 --- libraries/entities/src/EntityItemProperties.cpp | 3 --- 2 files changed, 6 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 12e79332c3..91231df3f5 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -782,9 +782,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); - -qDebug() << "EntityItem::setProperties().... localRenderAlpha:" << getLocalRenderAlpha(); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, setIgnoreForCollisions); SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, setCollisionsWillMove); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index ab2ca9b292..1080a866f5 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -206,9 +206,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localRenderAlpha, setLocalRenderAlpha); - -qDebug() << "EntityItemProperties::copyFromScriptValue().... localRenderAlpha:" << getLocalRenderAlpha(); - COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions); COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(collisionsWillMove, setCollisionsWillMove); From a04941bcd58aace3aef9eb8100efc7c8120de81c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 17:56:31 -0700 Subject: [PATCH 054/104] removed some old debug --- interface/src/ui/overlays/Circle3DOverlay.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 8cb65313ce..516a642639 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -174,13 +174,6 @@ void Circle3DOverlay::setProperties(const QScriptValue &properties) { if (innerRadius.isValid()) { setInnerRadius(innerRadius.toVariant().toFloat()); } - - qDebug() << "startAt:" << getStartAt(); - qDebug() << "endAt:" << getEndAt(); - qDebug() << "outerRadius:" << getOuterRadius(); - qDebug() << "innerRadius:" << getInnerRadius(); - qDebug() << "getIsSolid:" << getIsSolid(); - qDebug() << "getIsDashedLine:" << getIsDashedLine(); } From ab58a4e5b8e37b8b0e4d9b110d2ba6347e7daf80 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 17:58:18 -0700 Subject: [PATCH 055/104] tweaks to translation, reduce redraws of selection handles --- examples/libraries/entitySelectionTool.js | 46 +++++++++++++++++++++++ examples/newEditEntities.js | 24 ++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 1b97cc661f..c95ffbbb68 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -14,6 +14,9 @@ SelectionDisplay = (function () { var that = {}; + var lastAvatarPosition = MyAvatar.position; + var lastAvatarOrientation = MyAvatar.orientation; + var currentSelection = { id: -1, isKnownID: false }; var handleHoverColor = { red: 224, green: 67, blue: 36 }; @@ -88,6 +91,17 @@ SelectionDisplay = (function () { lineWidth: 1.0, }); + var grabberMoveUp = Overlays.addOverlay("billboard", { + url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/up-arrow.png", + position: { x:0, y: 0, z: 0}, + color: { red: 0, green: 0, blue: 0 }, + alpha: 1.0, + visible: false, + size: 0.1, + scale: 0.1, + isFacingAvatar: true + }); + var grabberLBN = Overlays.addOverlay("cube", grabberPropertiesCorner); var grabberRBN = Overlays.addOverlay("cube", grabberPropertiesCorner); var grabberLBF = Overlays.addOverlay("cube", grabberPropertiesCorner); @@ -206,6 +220,7 @@ SelectionDisplay = (function () { that.cleanup = function () { Overlays.deleteOverlay(highlightBox); Overlays.deleteOverlay(selectionBox); + Overlays.deleteOverlay(grabberMoveUp); Overlays.deleteOverlay(baseOfEntityProjectionOverlay); Overlays.deleteOverlay(grabberLBN); Overlays.deleteOverlay(grabberLBF); @@ -263,12 +278,19 @@ SelectionDisplay = (function () { }; that.select = function(entityID) { + +print("select()...... entityID:" + entityID.id); + var properties = Entities.getEntityProperties(entityID); if (currentSelection.isKnownID == true) { that.unselect(currentSelection); } currentSelection = entityID; + lastAvatarPosition = MyAvatar.position; + lastAvatarOrientation = MyAvatar.orientation; + + var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); var innerRadius = diagonal; @@ -283,6 +305,7 @@ SelectionDisplay = (function () { } var rotateHandleOffset = 0.05; + var grabberMoveUpOffset = 0.1; var left = properties.position.x - halfDimensions.x; var right = properties.position.x + halfDimensions.x; @@ -397,6 +420,8 @@ SelectionDisplay = (function () { z: far + rotateHandleOffset}; } } + + Overlays.editOverlay(highlightBox, { visible: false }); Overlays.editOverlay(selectionBox, { @@ -405,6 +430,9 @@ SelectionDisplay = (function () { dimensions: properties.dimensions, rotation: properties.rotation, }); + + + Overlays.editOverlay(grabberMoveUp, { visible: true, position: { x: center.x, y: top + grabberMoveUpOffset, z: center.z } }); Overlays.editOverlay(grabberLBN, { visible: true, position: { x: left, y: bottom, z: near } }); Overlays.editOverlay(grabberRBN, { visible: true, position: { x: right, y: bottom, z: near } }); @@ -507,6 +535,7 @@ SelectionDisplay = (function () { that.unselect = function (entityID) { Overlays.editOverlay(selectionBox, { visible: false }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: false }); + Overlays.editOverlay(grabberMoveUp, { visible: false }); Overlays.editOverlay(grabberLBN, { visible: false }); Overlays.editOverlay(grabberLBF, { visible: false }); Overlays.editOverlay(grabberRBN, { visible: false }); @@ -547,6 +576,23 @@ SelectionDisplay = (function () { Entities.editEntity(entityID, { localRenderAlpha: 1.0 }); }; + that.checkMove = function() { + if (currentSelection.isKnownID && + (!Vec3.equal(MyAvatar.position, lastAvatarPosition) || !Quat.equal(MyAvatar.orientation, lastAvatarOrientation))){ + +print("checkMove calling .... select()"); + +//print("Vec3.equal(MyAvatar.position, lastAvatarPosition):" + Vec3.equal(MyAvatar.position, lastAvatarPosition); +//Vec3.print("MyAvatar.position:", MyAvatar.position); +//Vec3.print("lastAvatarPosition:", lastAvatarPosition); + +//print("Quat.equal(MyAvatar.orientation, lastAvatarOrientation):" + Quat.equal(MyAvatar.orientation, lastAvatarOrientation)); +//Quat.print("MyAvatar.orientation:", MyAvatar.orientation); +//Quat.print("lastAvatarOrientation:", lastAvatarOrientation); + + that.select(currentSelection); + } + }; that.mousePressEvent = function(event) { }; diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index bf74ebc2aa..d8682dce5f 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -326,6 +326,7 @@ function isLocked(properties) { var entitySelected = false; +var moving = false; var selectedEntityID; var selectedEntityProperties; var mouseLastPosition; @@ -409,6 +410,7 @@ function mousePressEvent(event) { if (0 < x && sizeOK) { entitySelected = true; + moving = true; // if we are moving we are moving selectedEntityID = foundEntity; selectedEntityProperties = properties; orientation = MyAvatar.orientation; @@ -437,6 +439,7 @@ function mousePressEvent(event) { print("Clicked on " + selectedEntityID.id + " " + entitySelected); tooltip.updateText(selectedEntityProperties); tooltip.show(true); + print("mousePressEvent calling selectionDisplay.select()???"); selectionDisplay.select(selectedEntityID); } } @@ -477,20 +480,33 @@ function mouseMoveEvent(event) { return; } - if (entitySelected) { + if (entitySelected && moving) { pickRay = Camera.computePickRay(event.x, event.y); // translate mode left/right based on view toward entity var newIntersection = rayPlaneIntersection(pickRay, selectedEntityProperties.oldPosition, Quat.getFront(orientation)); + var vector = Vec3.subtract(newIntersection, intersection) + + // this allows us to use the old editModels "shifted" logic which makes the + // up/down behavior of the mouse move "in"/"out" of the screen. + var i = Vec3.dot(vector, Quat.getRight(orientation)); + var j = Vec3.dot(vector, Quat.getUp(orientation)); + vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i), + Vec3.multiply(Quat.getFront(orientation), j)); + + selectedEntityProperties.position = Vec3.sum(selectedEntityProperties.oldPosition, vector); Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); - selectionDisplay.highlightSelectable(selectedEntityID, selectedEntityProperties); // TODO: this should be more than highlighted + + // TODO: make this be a "moving state" - which is actually more like highlighted + // but including the change measurements + selectionDisplay.select(selectedEntityID); // TODO: this should be more than highlighted } } @@ -502,8 +518,7 @@ function mouseReleaseEvent(event) { if (entitySelected) { tooltip.show(false); } - - entitySelected = false; + moving = false; } Controller.mousePressEvent.connect(mousePressEvent); @@ -576,6 +591,7 @@ Script.scriptEnding.connect(function() { Script.update.connect(function (deltaTime) { toolBar.move(); progressDialog.move(); + selectionDisplay.checkMove(); }); function handeMenuEvent(menuItem) { From bf1c7b17fe03c2fea170e2a673f5d2762c32aee4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 2 Oct 2014 18:09:47 -0700 Subject: [PATCH 056/104] Fix for texture coordinate generation. --- .../resources/shaders/metavoxel_voxel_splat.frag | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/interface/resources/shaders/metavoxel_voxel_splat.frag b/interface/resources/shaders/metavoxel_voxel_splat.frag index c6c9860c1a..d0d0661164 100644 --- a/interface/resources/shaders/metavoxel_voxel_splat.frag +++ b/interface/resources/shaders/metavoxel_voxel_splat.frag @@ -26,11 +26,12 @@ varying vec4 alphaValues; void main(void) { // determine the cube face to use for texture coordinate generation vec3 absNormal = abs(normal); - vec2 parameters = step(absNormal.yy, absNormal.xz) * step(absNormal.zx, absNormal.xz); + vec3 steps = step(absNormal.zzy, absNormal.xyx); + vec2 parameters = mix(vec2(0.0, steps.y), vec2(steps.x, steps.x), steps.z); // blend the splat textures - gl_FragColor = (texture2D(diffuseMaps[0], mix(gl_TexCoord[0].xw, gl_TexCoord[0].zy, parameters)) * alphaValues.x + - texture2D(diffuseMaps[1], mix(gl_TexCoord[1].xw, gl_TexCoord[1].zy, parameters)) * alphaValues.y + - texture2D(diffuseMaps[2], mix(gl_TexCoord[2].xw, gl_TexCoord[2].zy, parameters)) * alphaValues.z + - texture2D(diffuseMaps[3], mix(gl_TexCoord[3].xw, gl_TexCoord[3].zy, parameters)) * alphaValues.w); + gl_FragColor = (texture2D(diffuseMaps[0], mix(gl_TexCoord[0].xy, gl_TexCoord[0].zw, parameters)) * alphaValues.x + + texture2D(diffuseMaps[1], mix(gl_TexCoord[1].xy, gl_TexCoord[1].zw, parameters)) * alphaValues.y + + texture2D(diffuseMaps[2], mix(gl_TexCoord[2].xy, gl_TexCoord[2].zw, parameters)) * alphaValues.z + + texture2D(diffuseMaps[3], mix(gl_TexCoord[3].xy, gl_TexCoord[3].zw, parameters)) * alphaValues.w); } From 175e5d5f798911a75ba8b265913008bb668661ed Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 20:44:49 -0700 Subject: [PATCH 057/104] add ray picking to 3D overlays --- examples/libraries/entitySelectionTool.js | 69 ++++++++++--- examples/newEditEntities.js | 3 + interface/src/Application.cpp | 1 + interface/src/ui/overlays/Base3DOverlay.cpp | 12 ++- interface/src/ui/overlays/Base3DOverlay.h | 5 +- .../src/ui/overlays/BillboardOverlay.cpp | 18 ++++ interface/src/ui/overlays/BillboardOverlay.h | 2 + interface/src/ui/overlays/Overlay.h | 1 - interface/src/ui/overlays/Overlays.cpp | 99 +++++++++++++++++++ interface/src/ui/overlays/Overlays.h | 21 +++- interface/src/ui/overlays/Volume3DOverlay.cpp | 10 ++ interface/src/ui/overlays/Volume3DOverlay.h | 3 + 12 files changed, 228 insertions(+), 16 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index c95ffbbb68..a4a5272142 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -14,6 +14,7 @@ SelectionDisplay = (function () { var that = {}; + var overlayNames = new Array(); var lastAvatarPosition = MyAvatar.position; var lastAvatarOrientation = MyAvatar.orientation; @@ -217,6 +218,48 @@ SelectionDisplay = (function () { isFacingAvatar: false }); + overlayNames[highlightBox] = "highlightBox"; + overlayNames[selectionBox] = "selectionBox"; + overlayNames[baseOfEntityProjectionOverlay] = "baseOfEntityProjectionOverlay"; + overlayNames[grabberMoveUp] = "grabberMoveUp"; + overlayNames[grabberLBN] = "grabberLBN"; + overlayNames[grabberLBF] = "grabberLBF"; + overlayNames[grabberRBN] = "grabberRBN"; + overlayNames[grabberRBF] = "grabberRBF"; + overlayNames[grabberLTN] = "grabberLTN"; + overlayNames[grabberLTF] = "grabberLTF"; + overlayNames[grabberRTN] = "grabberRTN"; + overlayNames[grabberRTF] = "grabberRTF"; + + overlayNames[grabberTOP] = "grabberTOP"; + overlayNames[grabberBOTTOM] = "grabberBOTTOM"; + overlayNames[grabberLEFT] = "grabberLEFT"; + overlayNames[grabberRIGHT] = "grabberRIGHT"; + overlayNames[grabberNEAR] = "grabberNEAR"; + overlayNames[grabberFAR] = "grabberFAR"; + + overlayNames[grabberEdgeTR] = "grabberEdgeTR"; + overlayNames[grabberEdgeTL] = "grabberEdgeTL"; + overlayNames[grabberEdgeTF] = "grabberEdgeTF"; + overlayNames[grabberEdgeTN] = "grabberEdgeTN"; + overlayNames[grabberEdgeBR] = "grabberEdgeBR"; + overlayNames[grabberEdgeBL] = "grabberEdgeBL"; + overlayNames[grabberEdgeBF] = "grabberEdgeBF"; + overlayNames[grabberEdgeBN] = "grabberEdgeBN"; + overlayNames[grabberEdgeNR] = "grabberEdgeNR"; + overlayNames[grabberEdgeNL] = "grabberEdgeNL"; + overlayNames[grabberEdgeFR] = "grabberEdgeFR"; + overlayNames[grabberEdgeFL] = "grabberEdgeFL"; + + overlayNames[yawHandle] = "yawHandle"; + overlayNames[pitchHandle] = "pitchHandle"; + overlayNames[rollHandle] = "rollHandle"; + + overlayNames[rotateOverlayInner] = "rotateOverlayInner"; + overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; + overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; + + that.cleanup = function () { Overlays.deleteOverlay(highlightBox); Overlays.deleteOverlay(selectionBox); @@ -579,22 +622,26 @@ print("select()...... entityID:" + entityID.id); that.checkMove = function() { if (currentSelection.isKnownID && (!Vec3.equal(MyAvatar.position, lastAvatarPosition) || !Quat.equal(MyAvatar.orientation, lastAvatarOrientation))){ - -print("checkMove calling .... select()"); - -//print("Vec3.equal(MyAvatar.position, lastAvatarPosition):" + Vec3.equal(MyAvatar.position, lastAvatarPosition); -//Vec3.print("MyAvatar.position:", MyAvatar.position); -//Vec3.print("lastAvatarPosition:", lastAvatarPosition); - -//print("Quat.equal(MyAvatar.orientation, lastAvatarOrientation):" + Quat.equal(MyAvatar.orientation, lastAvatarOrientation)); -//Quat.print("MyAvatar.orientation:", MyAvatar.orientation); -//Quat.print("lastAvatarOrientation:", lastAvatarOrientation); - that.select(currentSelection); } }; that.mousePressEvent = function(event) { + print("SelectionDisplay.mousePressEvent() x:" + event.x + " y:" + event.y); + var pickRay = Camera.computePickRay(event.x, event.y); + + var result = Overlays.findRayIntersection(pickRay); + if (result.intersects) { + print("something intersects... "); + print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); + + print(" result.intersects:" + result.intersects); + print(" result.overlayID:" + result.overlayID); + print(" result.distance:" + result.distance); + print(" result.face:" + result.face); + Vec3.print(" result.intersection:", result.intersection); + + } }; that.mouseMoveEvent = function(event) { diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index d8682dce5f..f1abf97b50 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -225,6 +225,9 @@ var toolBar = (function () { if (activeButton === toolBar.clicked(clickedOverlay)) { isActive = !isActive; + if (!isActive) { + selectionDisplay.unselectAll(); + } return true; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec40056299..485691e661 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3874,6 +3874,7 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser connect(scriptEngine, SIGNAL(loadScript(const QString&, bool)), this, SLOT(loadScript(const QString&, bool))); scriptEngine->registerGlobalObject("Overlays", &_overlays); + qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 9c67f56e0b..0e68347eca 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -25,8 +25,8 @@ Base3DOverlay::Base3DOverlay() : _position(DEFAULT_POSITION), _lineWidth(DEFAULT_LINE_WIDTH), _isSolid(DEFAULT_IS_SOLID), - _isDashedLine(DEFAULT_IS_DASHED_LINE), - _rotation() + _rotation(), + _isDashedLine(DEFAULT_IS_DASHED_LINE) { } @@ -114,6 +114,12 @@ void Base3DOverlay::setProperties(const QScriptValue& properties) { } } +bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + return false; +} + + void Base3DOverlay::drawDashedLine(const glm::vec3& start, const glm::vec3& end) { glBegin(GL_LINES); @@ -145,3 +151,5 @@ void Base3DOverlay::drawDashedLine(const glm::vec3& start, const glm::vec3& end) } + + diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index b6094be08f..62d6826ed0 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,7 +13,8 @@ #include #include -//#include + +#include #include "Overlay.h" @@ -42,6 +43,8 @@ public: virtual void setProperties(const QScriptValue& properties); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + protected: void drawDashedLine(const glm::vec3& start, const glm::vec3& end); diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 5c5fea2a14..d42fe533de 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -80,6 +80,7 @@ void BillboardOverlay::render() { glBegin(GL_QUADS); { glTexCoord2f((float)_fromImage.x() / (float)_size.width(), (float)_fromImage.y() / (float)_size.height()); + glVertex2f(-x, -y); glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(), (float)_fromImage.y() / (float)_size.height()); @@ -161,3 +162,20 @@ void BillboardOverlay::replyFinished() { _billboard = reply->readAll(); _isLoaded = true; } + +bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + + if (_billboardTexture) { + float maxSize = glm::max(_fromImage.width(), _fromImage.height()); + float x = _fromImage.width() / (2.0f * maxSize); + float y = -_fromImage.height() / (2.0f * maxSize); + float maxDimension = glm::max(x,y); + float scaledDimension = maxDimension * _scale; + glm::vec3 corner = getCenter() - glm::vec3(scaledDimension, scaledDimension, scaledDimension) ; + AACube myCube(corner, scaledDimension * 2.0f); + return myCube.findRayIntersection(origin, direction, distance, face); + } + return false; +} + diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h index c1731343bf..a0b76869b3 100644 --- a/interface/src/ui/overlays/BillboardOverlay.h +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -26,6 +26,8 @@ public: virtual void render(); virtual void setProperties(const QScriptValue& properties); void setClipFromSource(const QRect& bounds) { _fromImage = bounds; } + + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; private slots: void replyFinished(); diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 623cd4081c..dfa9b11d74 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -74,7 +74,6 @@ public: void setColorPulse(float value) { _colorPulse = value; } void setAlphaPulse(float value) { _alphaPulse = value; } - virtual void setProperties(const QScriptValue& properties); protected: diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 4f6c37e96d..8511d79aca 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -8,6 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include "BillboardOverlay.h" @@ -255,6 +256,104 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) { return 0; // not found } +RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray) { + float bestDistance = std::numeric_limits::max(); + RayToOverlayIntersectionResult result; + QMapIterator i(_overlays3D); + i.toBack(); + while (i.hasPrevious()) { + i.previous(); + unsigned int thisID = i.key(); + Base3DOverlay* thisOverlay = static_cast(i.value()); + if (thisOverlay->getVisible() && thisOverlay->isLoaded()) { + float thisDistance; + BoxFace thisFace; + if (thisOverlay->findRayIntersection(ray.origin, ray.direction, thisDistance, thisFace)) { + if (thisDistance < bestDistance) { + bestDistance = thisDistance; + result.intersects = true; + result.distance = thisDistance; + result.face = thisFace; + result.overlayID = thisID; + result.intersection = ray.origin + (ray.direction * thisDistance); + } + } + } + } + return result; +} + +RayToOverlayIntersectionResult::RayToOverlayIntersectionResult() : + intersects(false), + overlayID(-1), + distance(0), + face(), + intersection() +{ +} + +QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { + QScriptValue obj = engine->newObject(); + obj.setProperty("intersects", value.intersects); + obj.setProperty("overlayID", value.overlayID); + obj.setProperty("distance", value.distance); + + QString faceName = ""; + // handle BoxFace + switch (value.face) { + case MIN_X_FACE: + faceName = "MIN_X_FACE"; + break; + case MAX_X_FACE: + faceName = "MAX_X_FACE"; + break; + case MIN_Y_FACE: + faceName = "MIN_Y_FACE"; + break; + case MAX_Y_FACE: + faceName = "MAX_Y_FACE"; + break; + case MIN_Z_FACE: + faceName = "MIN_Z_FACE"; + break; + case MAX_Z_FACE: + faceName = "MAX_Z_FACE"; + break; + case UNKNOWN_FACE: + faceName = "UNKNOWN_FACE"; + break; + } + obj.setProperty("face", faceName); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); + obj.setProperty("intersection", intersection); + return obj; +} + +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + value.overlayID = object.property("overlayID").toVariant().toInt(); + value.distance = object.property("distance").toVariant().toFloat(); + + QString faceName = object.property("face").toVariant().toString(); + if (faceName == "MIN_X_FACE") { + value.face = MIN_X_FACE; + } else if (faceName == "MAX_X_FACE") { + value.face = MAX_X_FACE; + } else if (faceName == "MIN_Y_FACE") { + value.face = MIN_Y_FACE; + } else if (faceName == "MAX_Y_FACE") { + value.face = MAX_Y_FACE; + } else if (faceName == "MIN_Z_FACE") { + value.face = MIN_Z_FACE; + } else { + value.face = MAX_Z_FACE; + }; + QScriptValue intersection = object.property("intersection"); + if (intersection.isValid()) { + vec3FromScriptValue(intersection, value.intersection); + } +} + bool Overlays::isLoaded(unsigned int id) { QReadLocker lock(&_lock); Overlay* overlay = _overlays2D.value(id); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 8bd8224f82..6676994eed 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -15,6 +15,21 @@ #include "Overlay.h" +class RayToOverlayIntersectionResult { +public: + RayToOverlayIntersectionResult(); + bool intersects; + int overlayID; + float distance; + BoxFace face; + glm::vec3 intersection; +}; + +Q_DECLARE_METATYPE(RayToOverlayIntersectionResult); + +QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value); +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value); + class Overlays : public QObject { Q_OBJECT public: @@ -36,8 +51,11 @@ public slots: /// deletes a particle void deleteOverlay(unsigned int id); - /// returns the top most overlay at the screen point, or 0 if not overlay at that point + /// returns the top most 2D overlay at the screen point, or 0 if not overlay at that point unsigned int getOverlayAtPoint(const glm::vec2& point); + + /// returns details about the closest 3D Overlay hit by the pick ray + RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray); /// returns whether the overlay's assets are loaded or not bool isLoaded(unsigned int id); @@ -52,5 +70,6 @@ private: QReadWriteLock _deleteLock; }; + #endif // hifi_Overlays_h diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index 4dfeed33a1..11e3e5d8cf 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -12,6 +12,7 @@ #include "InterfaceConfig.h" #include +#include #include #include @@ -80,3 +81,12 @@ void Volume3DOverlay::setProperties(const QScriptValue& properties) { } } } + +bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + + // TODO: this is not exactly accurate because it doesn't properly handle rotation... but it's close enough for our + // current use cases. We do need to improve it to be more accurate + AABox myBox(getCorner(), _dimensions); + return myBox.findRayIntersection(origin, direction, distance, face); +} diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index 35c0567cc5..7cde169c30 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -30,6 +30,7 @@ public: // getters const glm::vec3& getCenter() const { return _position; } // TODO: consider adding registration point!! + glm::vec3 getCorner() const { return _position - (_dimensions * 0.5f); } // TODO: consider adding registration point!! const glm::vec3& getDimensions() const { return _dimensions; } // setters @@ -38,6 +39,8 @@ public: virtual void setProperties(const QScriptValue& properties); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + protected: glm::vec3 _dimensions; }; From 370d01b1f581e6ab9859de12317892767fb596f6 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 01:16:26 -0700 Subject: [PATCH 058/104] implement up/down translation --- examples/libraries/entitySelectionTool.js | 136 +++++++++++++++++++--- examples/newEditEntities.js | 7 +- 2 files changed, 123 insertions(+), 20 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index a4a5272142..74c0f347b1 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -14,11 +14,17 @@ SelectionDisplay = (function () { var that = {}; + var mode = "UNKNOWN"; var overlayNames = new Array(); var lastAvatarPosition = MyAvatar.position; var lastAvatarOrientation = MyAvatar.orientation; + var lastPlaneIntersection; + var initialY; var currentSelection = { id: -1, isKnownID: false }; + var entitySelected = false; + var selectedEntityProperties; + var selectedEntityPropertiesOriginalPosition; var handleHoverColor = { red: 224, green: 67, blue: 36 }; var handleHoverAlpha = 1.0; @@ -320,19 +326,36 @@ SelectionDisplay = (function () { Overlays.editOverlay(highlightBox,{ visible: false}); }; - that.select = function(entityID) { - -print("select()...... entityID:" + entityID.id); - + that.select = function(entityID, event) { var properties = Entities.getEntityProperties(entityID); if (currentSelection.isKnownID == true) { that.unselect(currentSelection); } currentSelection = entityID; - + entitySelected = true; + lastAvatarPosition = MyAvatar.position; lastAvatarOrientation = MyAvatar.orientation; + if (event !== false) { + selectedEntityProperties = properties; + selectedEntityPropertiesOriginalPosition = properties.position; + pickRay = Camera.computePickRay(event.x, event.y); + lastPlaneIntersection = rayPlaneIntersection(pickRay, properties.position, Quat.getFront(lastAvatarOrientation)); + initialY = event.y; + + var wantDebug = false; + if (wantDebug) { + print("select() with EVENT...... "); + print(" initialY:" + initialY); + print(" event.y:" + event.y); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" current position:", properties.position); + } + + + } var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); @@ -573,6 +596,7 @@ print("select()...... entityID:" + entityID.id); that.unselect(currentSelection); } currentSelection = { id: -1, isKnownID: false }; + entitySelected = false; }; that.unselect = function (entityID) { @@ -617,37 +641,117 @@ print("select()...... entityID:" + entityID.id); Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); Entities.editEntity(entityID, { localRenderAlpha: 1.0 }); + + currentSelection = { id: -1, isKnownID: false }; + entitySelected = false; + }; + + that.translateUpDown = function(event) { + if (entitySelected && mode == "TRANSLATE_UP_DOWN") { + var newY = initialY - event.y; // y goes from top to bottom of window + + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + // we only care about the Y axis + vector.x = 0; + vector.z = 0; + + newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, vector); + + var wantDebug = false; + if (wantDebug) { + print("translateUpDown... "); + print(" initialY:" + initialY); + print(" event.y:" + event.y); + print(" newY:" + newY); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" recentPosition:", selectedEntityProperties.position); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + Entities.editEntity(selectedEntityID, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(selectedEntityID, false); // TODO: this should be more than highlighted + } }; that.checkMove = function() { if (currentSelection.isKnownID && (!Vec3.equal(MyAvatar.position, lastAvatarPosition) || !Quat.equal(MyAvatar.orientation, lastAvatarOrientation))){ - that.select(currentSelection); + that.select(currentSelection, false); } }; that.mousePressEvent = function(event) { - print("SelectionDisplay.mousePressEvent() x:" + event.x + " y:" + event.y); var pickRay = Camera.computePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); if (result.intersects) { - print("something intersects... "); - print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); - print(" result.intersects:" + result.intersects); - print(" result.overlayID:" + result.overlayID); - print(" result.distance:" + result.distance); - print(" result.face:" + result.face); - Vec3.print(" result.intersection:", result.intersection); - + var wantDebug = false; + if (wantDebug) { + print("something intersects... "); + print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); + print(" result.intersects:" + result.intersects); + print(" result.overlayID:" + result.overlayID); + print(" result.distance:" + result.distance); + print(" result.face:" + result.face); + Vec3.print(" result.intersection:", result.intersection); + } + + switch(result.overlayID) { + case grabberMoveUp: + mode = "TRANSLATE_UP_DOWN"; + + pickRay = Camera.computePickRay(event.x, event.y); + lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + initialY = event.y; + + if (wantDebug) { + print("mousePressEvent()...... "); + print(" initialY:" + initialY); + print(" event.y:" + event.y); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + } + + break; + + default: + mode = "UNKNOWN"; + break; + } + + //print(" mode:" + mode); + } }; that.mouseMoveEvent = function(event) { + //print("mouseMoveEvent()... mode:" + mode); + switch (mode) { + case "TRANSLATE_UP_DOWN": + that.translateUpDown(event); + break; + default: + // nothing to do by default + break; + } }; that.mouseReleaseEvent = function(event) { + mode = "UNKNOWN"; }; Controller.mousePressEvent.connect(that.mousePressEvent); diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index f1abf97b50..f2aefda055 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -442,8 +442,7 @@ function mousePressEvent(event) { print("Clicked on " + selectedEntityID.id + " " + entitySelected); tooltip.updateText(selectedEntityProperties); tooltip.show(true); - print("mousePressEvent calling selectionDisplay.select()???"); - selectionDisplay.select(selectedEntityID); + selectionDisplay.select(selectedEntityID, event); } } @@ -491,7 +490,7 @@ function mouseMoveEvent(event) { selectedEntityProperties.oldPosition, Quat.getFront(orientation)); - var vector = Vec3.subtract(newIntersection, intersection) + var vector = Vec3.subtract(newIntersection, intersection); // this allows us to use the old editModels "shifted" logic which makes the // up/down behavior of the mouse move "in"/"out" of the screen. @@ -509,7 +508,7 @@ function mouseMoveEvent(event) { // TODO: make this be a "moving state" - which is actually more like highlighted // but including the change measurements - selectionDisplay.select(selectedEntityID); // TODO: this should be more than highlighted + selectionDisplay.select(selectedEntityID, event); // TODO: this should be more than highlighted } } From 05f2263c9c62eaf123ea10c53ff27a4338e85fff Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 02:31:45 -0700 Subject: [PATCH 059/104] implement NEAR face stretch --- examples/libraries/entitySelectionTool.js | 255 +++++++++++++++++----- 1 file changed, 206 insertions(+), 49 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 74c0f347b1..d0ee71038f 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -19,12 +19,12 @@ SelectionDisplay = (function () { var lastAvatarPosition = MyAvatar.position; var lastAvatarOrientation = MyAvatar.orientation; var lastPlaneIntersection; - var initialY; var currentSelection = { id: -1, isKnownID: false }; var entitySelected = false; var selectedEntityProperties; var selectedEntityPropertiesOriginalPosition; + var selectedEntityPropertiesOriginalDimensions; var handleHoverColor = { red: 224, green: 67, blue: 36 }; var handleHoverAlpha = 1.0; @@ -340,17 +340,17 @@ SelectionDisplay = (function () { if (event !== false) { selectedEntityProperties = properties; selectedEntityPropertiesOriginalPosition = properties.position; + selectedEntityPropertiesOriginalDimensions = properties.dimensions; pickRay = Camera.computePickRay(event.x, event.y); lastPlaneIntersection = rayPlaneIntersection(pickRay, properties.position, Quat.getFront(lastAvatarOrientation)); - initialY = event.y; - var wantDebug = false; + var wantDebug = true; if (wantDebug) { print("select() with EVENT...... "); - print(" initialY:" + initialY); print(" event.y:" + event.y); Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" current position:", properties.position); } @@ -584,9 +584,9 @@ SelectionDisplay = (function () { innerRadius: 0.9 }); - Overlays.editOverlay(yawHandle, { visible: true, position: yawCorner, rotation: yawHandleRotation}); - Overlays.editOverlay(pitchHandle, { visible: true, position: pitchCorner, rotation: pitchHandleRotation}); - Overlays.editOverlay(rollHandle, { visible: true, position: rollCorner, rotation: rollHandleRotation}); + Overlays.editOverlay(yawHandle, { visible: false, position: yawCorner, rotation: yawHandleRotation}); + Overlays.editOverlay(pitchHandle, { visible: false, position: pitchCorner, rotation: pitchHandleRotation}); + Overlays.editOverlay(rollHandle, { visible: false, position: rollCorner, rotation: rollHandleRotation}); Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; @@ -647,43 +647,164 @@ SelectionDisplay = (function () { }; that.translateUpDown = function(event) { - if (entitySelected && mode == "TRANSLATE_UP_DOWN") { - var newY = initialY - event.y; // y goes from top to bottom of window - - pickRay = Camera.computePickRay(event.x, event.y); - - // translate mode left/right based on view toward entity - var newIntersection = rayPlaneIntersection(pickRay, - selectedEntityPropertiesOriginalPosition, - Quat.getFront(lastAvatarOrientation)); - - var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); - - // we only care about the Y axis - vector.x = 0; - vector.z = 0; - - newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, vector); - - var wantDebug = false; - if (wantDebug) { - print("translateUpDown... "); - print(" initialY:" + initialY); - print(" event.y:" + event.y); - print(" newY:" + newY); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" newIntersection:", newIntersection); - Vec3.print(" vector:", vector); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); - Vec3.print(" recentPosition:", selectedEntityProperties.position); - Vec3.print(" newPosition:", newPosition); - } - - selectedEntityProperties.position = newPosition; - Entities.editEntity(selectedEntityID, selectedEntityProperties); - tooltip.updateText(selectedEntityProperties); - that.select(selectedEntityID, false); // TODO: this should be more than highlighted + if (!entitySelected || mode !== "TRANSLATE_UP_DOWN") { + return; // not allowed } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + // we only care about the Y axis + vector.x = 0; + vector.z = 0; + + newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, vector); + + var wantDebug = false; + if (wantDebug) { + print("translateUpDown... "); + print(" event.y:" + event.y); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" recentPosition:", selectedEntityProperties.position); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + Entities.editEntity(selectedEntityID, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(selectedEntityID, false); // TODO: this should be more than highlighted + }; + + that.stretchNEAR = function(event) { + if (!entitySelected || mode !== "STRETCH_NEAR") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var originalNEAR = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; + var newNEAR = originalNEAR + vector.z; + + // if near is changing, then... + // dimensions changes by: (oldNEAR - newNEAR) + var changeInDimensions = { x: 0, y: 0, z: (originalNEAR - newNEAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: 0, y: 0, z: (originalNEAR - newNEAR) * -0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchNEAR... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" originalNEAR:" + originalNEAR); + print(" newNEAR:" + newNEAR); + Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(selectedEntityID, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(selectedEntityID, false); // TODO: this should be more than highlighted + }; + + that.stretchRBN = function(event) { + if (!entitySelected || mode !== "STRETCH_RBN") { + return; // not allowed + } + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var right = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; + var bottom = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; + var near = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; + var originalRBN = { x: right, y: bottom, z: near }; + + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + // calculate the X/Z axis change... and use that instead of the X/Y axis change + var i = Vec3.dot(vector, Quat.getRight(orientation)); + var j = Vec3.dot(vector, Quat.getUp(orientation)); + vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i), + Vec3.multiply(Quat.getFront(orientation), j)); + + newRBN = Vec3.sum(originalRBN, vector); + + var oldDimensions = selectedEntityPropertiesOriginalDimensions; + var changeInDimensions = Vec3.subtract(newRBN, originalRBN); + var newDimensions = Vec3.sum(selectedEntityProperties.dimensions, changeInDimensions); + if (newDimensions.x < 0) { + // TODO: need to handle x flip for position + newDimensions.x = Math.abs(newDimensions.x); + } + + if (newDimensions.y < 0) { + // TODO: need to handle y flip for position + newDimensions.y = Math.abs(newDimensions.y); + } + + if (newDimensions.z < 0) { + // TODO: need to handle z flip for position + newDimensions.z = Math.abs(newDimensions.z); + } + changeInDimensions = Vec3.subtract(newDimensions, oldDimensions); + + // TODO: need to handle registrations, for now assume center registration + var changeInPosition = Vec3.multiply(changeInDimensions, 0.5); + newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + + + var wantDebug = true; + if (wantDebug) { + print("stretchRBN... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + Vec3.print(" original RBN:", originalRBN); + Vec3.print(" new RBN:", newRBN); + Vec3.print(" SEP.OriginalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" oldDimensions:", oldDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(selectedEntityID, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(selectedEntityID, false); // TODO: this should be more than highlighted }; that.checkMove = function() { @@ -698,7 +819,7 @@ SelectionDisplay = (function () { var result = Overlays.findRayIntersection(pickRay); if (result.intersects) { - var wantDebug = false; + var wantDebug = true; if (wantDebug) { print("something intersects... "); print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); @@ -716,11 +837,8 @@ SelectionDisplay = (function () { pickRay = Camera.computePickRay(event.x, event.y); lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, Quat.getFront(lastAvatarOrientation)); - initialY = event.y; - if (wantDebug) { - print("mousePressEvent()...... "); - print(" initialY:" + initialY); + print("mousePressEvent()...... " + overlayNames[result.overlayID]); print(" event.y:" + event.y); Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); @@ -728,6 +846,39 @@ SelectionDisplay = (function () { break; + case grabberRBN: + mode = "STRETCH_RBN"; + + pickRay = Camera.computePickRay(event.x, event.y); + lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + if (wantDebug) { + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + print(" event.y:" + event.y); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + } + + break; + + case grabberNEAR: + case grabberEdgeTN: + case grabberEdgeBN: + mode = "STRETCH_NEAR"; + + pickRay = Camera.computePickRay(event.x, event.y); + lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + if (wantDebug) { + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + print(" event.y:" + event.y); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + print(" mode:" + mode); + } + + break; + default: mode = "UNKNOWN"; break; @@ -739,11 +890,17 @@ SelectionDisplay = (function () { }; that.mouseMoveEvent = function(event) { - //print("mouseMoveEvent()... mode:" + mode); + print("mouseMoveEvent()... mode:" + mode); switch (mode) { case "TRANSLATE_UP_DOWN": that.translateUpDown(event); break; + case "STRETCH_RBN": + that.stretchRBN(event); + break; + case "STRETCH_NEAR": + that.stretchNEAR(event); + break; default: // nothing to do by default break; From f59185c00b6900899f6fefffdfaaf0b5a310a140 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 03:02:54 -0700 Subject: [PATCH 060/104] allow 3d overlays to be ignored from ray intersection --- interface/src/ui/overlays/Base3DOverlay.cpp | 6 +++++- interface/src/ui/overlays/Base3DOverlay.h | 3 +++ interface/src/ui/overlays/Overlays.cpp | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 0e68347eca..f58dc8e29e 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -26,7 +26,8 @@ Base3DOverlay::Base3DOverlay() : _lineWidth(DEFAULT_LINE_WIDTH), _isSolid(DEFAULT_IS_SOLID), _rotation(), - _isDashedLine(DEFAULT_IS_DASHED_LINE) + _isDashedLine(DEFAULT_IS_DASHED_LINE), + _ignoreRayIntersection(false) { } @@ -112,6 +113,9 @@ void Base3DOverlay::setProperties(const QScriptValue& properties) { if (properties.property("dashed").isValid()) { setIsDashedLine(properties.property("dashed").toVariant().toBool()); } + if (properties.property("ignoreRayIntersection").isValid()) { + setIgnoreRayIntersection(properties.property("ignoreRayIntersection").toVariant().toBool()); + } } bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 62d6826ed0..e293bae5a9 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -33,6 +33,7 @@ public: bool getIsDashedLine() const { return _isDashedLine; } bool getIsSolidLine() const { return !_isDashedLine; } const glm::quat& getRotation() const { return _rotation; } + bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } // setters void setPosition(const glm::vec3& position) { _position = position; } @@ -40,6 +41,7 @@ public: void setIsSolid(bool isSolid) { _isSolid = isSolid; } void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } void setRotation(const glm::quat& value) { _rotation = value; } + void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } virtual void setProperties(const QScriptValue& properties); @@ -53,6 +55,7 @@ protected: glm::quat _rotation; bool _isSolid; bool _isDashedLine; + bool _ignoreRayIntersection; }; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 8511d79aca..3ba3f66f1a 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -265,7 +265,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray) i.previous(); unsigned int thisID = i.key(); Base3DOverlay* thisOverlay = static_cast(i.value()); - if (thisOverlay->getVisible() && thisOverlay->isLoaded()) { + if (thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) { float thisDistance; BoxFace thisFace; if (thisOverlay->findRayIntersection(ray.origin, ray.direction, thisDistance, thisFace)) { From 4af3968e012a51e0f59a218f267525a71e3b4071 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 03:03:48 -0700 Subject: [PATCH 061/104] move XZ translation into the selection tool so that it plays nice with stretchers --- examples/libraries/entitySelectionTool.js | 135 +++++++++++++++++++--- examples/newEditEntities.js | 32 ----- 2 files changed, 120 insertions(+), 47 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index d0ee71038f..48477765c5 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -85,6 +85,7 @@ SelectionDisplay = (function () { visible: false, dashed: true, lineWidth: 1.0, + ignoreRayIntersection: true // this never ray intersects }); var selectionBox = Overlays.addOverlay("cube", { @@ -584,9 +585,9 @@ SelectionDisplay = (function () { innerRadius: 0.9 }); - Overlays.editOverlay(yawHandle, { visible: false, position: yawCorner, rotation: yawHandleRotation}); - Overlays.editOverlay(pitchHandle, { visible: false, position: pitchCorner, rotation: pitchHandleRotation}); - Overlays.editOverlay(rollHandle, { visible: false, position: rollCorner, rotation: rollHandleRotation}); + Overlays.editOverlay(yawHandle, { visible: true, position: yawCorner, rotation: yawHandleRotation}); + Overlays.editOverlay(pitchHandle, { visible: true, position: pitchCorner, rotation: pitchHandleRotation}); + Overlays.editOverlay(rollHandle, { visible: true, position: rollCorner, rotation: rollHandleRotation}); Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; @@ -645,6 +646,45 @@ SelectionDisplay = (function () { currentSelection = { id: -1, isKnownID: false }; entitySelected = false; }; + + that.translateXZ = function(event) { + if (!entitySelected || mode !== "TRANSLATE_XZ") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + // this allows us to use the old editModels "shifted" logic which makes the + // up/down behavior of the mouse move "in"/"out" of the screen. + var i = Vec3.dot(vector, Quat.getRight(orientation)); + var j = Vec3.dot(vector, Quat.getUp(orientation)); + vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i), + Vec3.multiply(Quat.getFront(orientation), j)); + + newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, vector); + + var wantDebug = false; + if (wantDebug) { + print("translateXZ... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" recentPosition:", selectedEntityProperties.position); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; that.translateUpDown = function(event) { if (!entitySelected || mode !== "TRANSLATE_UP_DOWN") { @@ -678,9 +718,9 @@ SelectionDisplay = (function () { } selectedEntityProperties.position = newPosition; - Entities.editEntity(selectedEntityID, selectedEntityProperties); + Entities.editEntity(currentSelection, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); - that.select(selectedEntityID, false); // TODO: this should be more than highlighted + that.select(currentSelection, false); // TODO: this should be more than highlighted }; that.stretchNEAR = function(event) { @@ -726,9 +766,9 @@ SelectionDisplay = (function () { selectedEntityProperties.position = newPosition; selectedEntityProperties.dimensions = newDimensions; - Entities.editEntity(selectedEntityID, selectedEntityProperties); + Entities.editEntity(currentSelection, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); - that.select(selectedEntityID, false); // TODO: this should be more than highlighted + that.select(currentSelection, false); // TODO: this should be more than highlighted }; that.stretchRBN = function(event) { @@ -802,9 +842,9 @@ SelectionDisplay = (function () { selectedEntityProperties.position = newPosition; selectedEntityProperties.dimensions = newDimensions; - Entities.editEntity(selectedEntityID, selectedEntityProperties); + Entities.editEntity(currentSelection, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); - that.select(selectedEntityID, false); // TODO: this should be more than highlighted + that.select(currentSelection, false); // TODO: this should be more than highlighted }; that.checkMove = function() { @@ -815,8 +855,16 @@ SelectionDisplay = (function () { }; that.mousePressEvent = function(event) { + var somethingClicked = false; var pickRay = Camera.computePickRay(event.x, event.y); + + // before we do a ray test for grabbers, disable the ray intersection for our selection box + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true }); + Overlays.editOverlay(yawHandle, { ignoreRayIntersection: true }); + Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: true }); + Overlays.editOverlay(rollHandle, { ignoreRayIntersection: true }); var result = Overlays.findRayIntersection(pickRay); + if (result.intersects) { var wantDebug = true; @@ -843,7 +891,7 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); } - + somethingClicked = true; break; case grabberRBN: @@ -858,7 +906,7 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); } - + somethingClicked = true; break; case grabberNEAR: @@ -876,17 +924,64 @@ SelectionDisplay = (function () { Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); print(" mode:" + mode); } - + somethingClicked = true; break; default: mode = "UNKNOWN"; break; } - - //print(" mode:" + mode); - } + + if (!somethingClicked) { + // After testing our stretch handles, then check out rotate handles + Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false }); + Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false }); + Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false }); + var result = Overlays.findRayIntersection(pickRay); + if (result.intersects) { + switch(result.overlayID) { + default: + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + mode = "UNKNOWN"; + break; + } + } + } + + if (!somethingClicked) { + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: false }); + var result = Overlays.findRayIntersection(pickRay); + if (result.intersects) { + switch(result.overlayID) { + case selectionBox: + mode = "TRANSLATE_XZ"; + + pickRay = Camera.computePickRay(event.x, event.y); + lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + if (wantDebug) { + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + print(" event.y:" + event.y); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + } + somethingClicked = true; + break; + default: + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + mode = "UNKNOWN"; + break; + } + } + } + + // reset everything as intersectable... + // TODO: we could optimize this since some of these were already flipped back + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: false }); + Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false }); + Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false }); + Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false }); }; that.mouseMoveEvent = function(event) { @@ -895,6 +990,9 @@ SelectionDisplay = (function () { case "TRANSLATE_UP_DOWN": that.translateUpDown(event); break; + case "TRANSLATE_XZ": + that.translateXZ(event); + break; case "STRETCH_RBN": that.stretchRBN(event); break; @@ -909,6 +1007,13 @@ SelectionDisplay = (function () { that.mouseReleaseEvent = function(event) { mode = "UNKNOWN"; + + // if something is selected, then reset the "original" properties for any potential next click+move operation + if (entitySelected) { + selectedEntityProperties = Entities.getEntityProperties(currentSelection); + selectedEntityPropertiesOriginalPosition = properties.position; + selectedEntityPropertiesOriginalDimensions = properties.dimensions; + } }; Controller.mousePressEvent.connect(that.mousePressEvent); diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index f2aefda055..c1627df7a2 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -329,7 +329,6 @@ function isLocked(properties) { var entitySelected = false; -var moving = false; var selectedEntityID; var selectedEntityProperties; var mouseLastPosition; @@ -413,7 +412,6 @@ function mousePressEvent(event) { if (0 < x && sizeOK) { entitySelected = true; - moving = true; // if we are moving we are moving selectedEntityID = foundEntity; selectedEntityProperties = properties; orientation = MyAvatar.orientation; @@ -481,35 +479,6 @@ function mouseMoveEvent(event) { } return; } - - if (entitySelected && moving) { - pickRay = Camera.computePickRay(event.x, event.y); - - // translate mode left/right based on view toward entity - var newIntersection = rayPlaneIntersection(pickRay, - selectedEntityProperties.oldPosition, - Quat.getFront(orientation)); - - var vector = Vec3.subtract(newIntersection, intersection); - - // this allows us to use the old editModels "shifted" logic which makes the - // up/down behavior of the mouse move "in"/"out" of the screen. - var i = Vec3.dot(vector, Quat.getRight(orientation)); - var j = Vec3.dot(vector, Quat.getUp(orientation)); - vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i), - Vec3.multiply(Quat.getFront(orientation), j)); - - - selectedEntityProperties.position = Vec3.sum(selectedEntityProperties.oldPosition, vector); - - - Entities.editEntity(selectedEntityID, selectedEntityProperties); - tooltip.updateText(selectedEntityProperties); - - // TODO: make this be a "moving state" - which is actually more like highlighted - // but including the change measurements - selectionDisplay.select(selectedEntityID, event); // TODO: this should be more than highlighted - } } @@ -520,7 +489,6 @@ function mouseReleaseEvent(event) { if (entitySelected) { tooltip.show(false); } - moving = false; } Controller.mousePressEvent.connect(mousePressEvent); From 88c983cd3ba3ce0922d21f5627472abc57a9fa1f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 03:12:09 -0700 Subject: [PATCH 062/104] dry up some code --- examples/libraries/entitySelectionTool.js | 52 +++++------------------ 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 48477765c5..53ee4119a4 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -881,31 +881,11 @@ SelectionDisplay = (function () { switch(result.overlayID) { case grabberMoveUp: mode = "TRANSLATE_UP_DOWN"; - - pickRay = Camera.computePickRay(event.x, event.y); - lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, - Quat.getFront(lastAvatarOrientation)); - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - print(" event.y:" + event.y); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); - } somethingClicked = true; break; case grabberRBN: mode = "STRETCH_RBN"; - - pickRay = Camera.computePickRay(event.x, event.y); - lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, - Quat.getFront(lastAvatarOrientation)); - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - print(" event.y:" + event.y); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); - } somethingClicked = true; break; @@ -913,17 +893,6 @@ SelectionDisplay = (function () { case grabberEdgeTN: case grabberEdgeBN: mode = "STRETCH_NEAR"; - - pickRay = Camera.computePickRay(event.x, event.y); - lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, - Quat.getFront(lastAvatarOrientation)); - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - print(" event.y:" + event.y); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); - print(" mode:" + mode); - } somethingClicked = true; break; @@ -956,16 +925,6 @@ SelectionDisplay = (function () { switch(result.overlayID) { case selectionBox: mode = "TRANSLATE_XZ"; - - pickRay = Camera.computePickRay(event.x, event.y); - lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, - Quat.getFront(lastAvatarOrientation)); - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - print(" event.y:" + event.y); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); - } somethingClicked = true; break; default: @@ -976,6 +935,17 @@ SelectionDisplay = (function () { } } + if (somethingClicked) { + pickRay = Camera.computePickRay(event.x, event.y); + lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + if (wantDebug) { + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + } + } + // reset everything as intersectable... // TODO: we could optimize this since some of these were already flipped back Overlays.editOverlay(selectionBox, { ignoreRayIntersection: false }); From 6207e951b6f5135be73f9b61fa02ae3fc85785ee Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 03:17:45 -0700 Subject: [PATCH 063/104] implement FAR side stretching --- examples/libraries/entitySelectionTool.js | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 53ee4119a4..763fc69025 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -771,6 +771,51 @@ SelectionDisplay = (function () { that.select(currentSelection, false); // TODO: this should be more than highlighted }; + that.stretchFAR = function(event) { + if (!entitySelected || mode !== "STRETCH_FAR") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var originalFAR = selectedEntityPropertiesOriginalPosition.z + halfDimensions.z; + var newFAR = originalFAR + vector.z; + var changeInDimensions = { x: 0, y: 0, z: (newFAR - originalFAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: 0, y: 0, z: (newFAR - originalFAR) * 0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchFAR... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" originalFAR:" + originalFAR); + print(" newFAR:" + newFAR); + Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + that.stretchRBN = function(event) { if (!entitySelected || mode !== "STRETCH_RBN") { return; // not allowed @@ -896,6 +941,13 @@ SelectionDisplay = (function () { somethingClicked = true; break; + case grabberFAR: + case grabberEdgeTF: + case grabberEdgeBF: + mode = "STRETCH_FAR"; + somethingClicked = true; + break; + default: mode = "UNKNOWN"; break; @@ -969,6 +1021,9 @@ SelectionDisplay = (function () { case "STRETCH_NEAR": that.stretchNEAR(event); break; + case "STRETCH_FAR": + that.stretchFAR(event); + break; default: // nothing to do by default break; From ea0d74b957bacdd715b2fcfccf5b446f021230cb Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 03:33:32 -0700 Subject: [PATCH 064/104] implement TOP, BOTTOM, LEFT, RIGHT side stretching --- examples/libraries/entitySelectionTool.js | 212 +++++++++++++++++++++- 1 file changed, 208 insertions(+), 4 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 763fc69025..f556182d22 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -816,6 +816,182 @@ SelectionDisplay = (function () { that.select(currentSelection, false); // TODO: this should be more than highlighted }; + that.stretchTOP = function(event) { + if (!entitySelected || mode !== "STRETCH_TOP") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var originalTOP = selectedEntityPropertiesOriginalPosition.y + halfDimensions.y; + var newTOP = originalTOP + vector.y; + var changeInDimensions = { x: 0, y: (newTOP - originalTOP), z: 0 }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: 0, y: (newTOP - originalTOP) * 0.5, z: 0 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchTOP... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" originalTOP:" + originalTOP); + print(" newTOP:" + newTOP); + Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchBOTTOM = function(event) { + if (!entitySelected || mode !== "STRETCH_BOTTOM") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var originalBOTTOM = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; + var newBOTTOM = originalBOTTOM + vector.y; + var changeInDimensions = { x: 0, y: (originalBOTTOM - newBOTTOM), z: 0 }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: 0, y: (originalBOTTOM - newBOTTOM) * -0.5, z: 0 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchBOTTOM... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" originalBOTTOM:" + originalBOTTOM); + print(" newBOTTOM:" + newBOTTOM); + Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchRIGHT = function(event) { + if (!entitySelected || mode !== "STRETCH_RIGHT") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var originalRIGHT = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; + var newRIGHT = originalRIGHT + vector.x; + var changeInDimensions = { x: (newRIGHT - originalRIGHT), y: 0 , z: 0 }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newRIGHT - originalRIGHT) * 0.5, y: 0, z: 0 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchRIGHT... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" originalRIGHT:" + originalRIGHT); + print(" newRIGHT:" + newRIGHT); + Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchLEFT = function(event) { + if (!entitySelected || mode !== "STRETCH_LEFT") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var originalLEFT = selectedEntityPropertiesOriginalPosition.x - halfDimensions.x; + var newLEFT = originalLEFT + vector.x; + var changeInDimensions = { x: (originalLEFT - newLEFT), y: 0, z: 0 }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (originalLEFT - newLEFT) * -0.5, y: 0, z: 0 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchLEFT... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" originalLEFT:" + originalLEFT); + print(" newLEFT:" + newLEFT); + Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + that.stretchRBN = function(event) { if (!entitySelected || mode !== "STRETCH_RBN") { return; // not allowed @@ -935,18 +1111,34 @@ SelectionDisplay = (function () { break; case grabberNEAR: - case grabberEdgeTN: - case grabberEdgeBN: + case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? + case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_NEAR"; somethingClicked = true; break; case grabberFAR: - case grabberEdgeTF: - case grabberEdgeBF: + case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? + case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_FAR"; somethingClicked = true; break; + case grabberTOP: + mode = "STRETCH_TOP"; + somethingClicked = true; + break; + case grabberBOTTOM: + mode = "STRETCH_BOTTOM"; + somethingClicked = true; + break; + case grabberRIGHT: + mode = "STRETCH_RIGHT"; + somethingClicked = true; + break; + case grabberLEFT: + mode = "STRETCH_LEFT"; + somethingClicked = true; + break; default: mode = "UNKNOWN"; @@ -1024,6 +1216,18 @@ SelectionDisplay = (function () { case "STRETCH_FAR": that.stretchFAR(event); break; + case "STRETCH_TOP": + that.stretchTOP(event); + break; + case "STRETCH_BOTTOM": + that.stretchBOTTOM(event); + break; + case "STRETCH_RIGHT": + that.stretchRIGHT(event); + break; + case "STRETCH_LEFT": + that.stretchLEFT(event); + break; default: // nothing to do by default break; From f6fc1173e6c93714bc15d8ff41b1f359ccc3b870 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 03:52:57 -0700 Subject: [PATCH 065/104] fix RBN stretching, tweak some names, add some additional edge grabbers to some sides --- examples/libraries/entitySelectionTool.js | 162 ++++++++++------------ 1 file changed, 72 insertions(+), 90 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index f556182d22..1651fadcd4 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -737,14 +737,14 @@ SelectionDisplay = (function () { var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); - var originalNEAR = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; - var newNEAR = originalNEAR + vector.z; + var oldNEAR = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; + var newNEAR = oldNEAR + vector.z; // if near is changing, then... // dimensions changes by: (oldNEAR - newNEAR) - var changeInDimensions = { x: 0, y: 0, z: (originalNEAR - newNEAR) }; + var changeInDimensions = { x: 0, y: 0, z: (oldNEAR - newNEAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); - var changeInPosition = { x: 0, y: 0, z: (originalNEAR - newNEAR) * -0.5 }; + var changeInPosition = { x: 0, y: 0, z: (oldNEAR - newNEAR) * -0.5 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; if (wantDebug) { @@ -752,13 +752,13 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - print(" originalNEAR:" + originalNEAR); + print(" oldNEAR:" + oldNEAR); print(" newNEAR:" + newNEAR); - Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } @@ -785,11 +785,11 @@ SelectionDisplay = (function () { var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); - var originalFAR = selectedEntityPropertiesOriginalPosition.z + halfDimensions.z; - var newFAR = originalFAR + vector.z; - var changeInDimensions = { x: 0, y: 0, z: (newFAR - originalFAR) }; + var oldFAR = selectedEntityPropertiesOriginalPosition.z + halfDimensions.z; + var newFAR = oldFAR + vector.z; + var changeInDimensions = { x: 0, y: 0, z: (newFAR - oldFAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); - var changeInPosition = { x: 0, y: 0, z: (newFAR - originalFAR) * 0.5 }; + var changeInPosition = { x: 0, y: 0, z: (newFAR - oldFAR) * 0.5 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; if (wantDebug) { @@ -797,13 +797,13 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - print(" originalFAR:" + originalFAR); + print(" oldFAR:" + oldFAR); print(" newFAR:" + newFAR); - Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } @@ -830,11 +830,11 @@ SelectionDisplay = (function () { var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); - var originalTOP = selectedEntityPropertiesOriginalPosition.y + halfDimensions.y; - var newTOP = originalTOP + vector.y; - var changeInDimensions = { x: 0, y: (newTOP - originalTOP), z: 0 }; + var oldTOP = selectedEntityPropertiesOriginalPosition.y + halfDimensions.y; + var newTOP = oldTOP + vector.y; + var changeInDimensions = { x: 0, y: (newTOP - oldTOP), z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); - var changeInPosition = { x: 0, y: (newTOP - originalTOP) * 0.5, z: 0 }; + var changeInPosition = { x: 0, y: (newTOP - oldTOP) * 0.5, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; if (wantDebug) { @@ -842,13 +842,13 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - print(" originalTOP:" + originalTOP); + print(" oldTOP:" + oldTOP); print(" newTOP:" + newTOP); - Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } @@ -874,11 +874,11 @@ SelectionDisplay = (function () { var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); - var originalBOTTOM = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; - var newBOTTOM = originalBOTTOM + vector.y; - var changeInDimensions = { x: 0, y: (originalBOTTOM - newBOTTOM), z: 0 }; + var oldBOTTOM = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; + var newBOTTOM = oldBOTTOM + vector.y; + var changeInDimensions = { x: 0, y: (oldBOTTOM - newBOTTOM), z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); - var changeInPosition = { x: 0, y: (originalBOTTOM - newBOTTOM) * -0.5, z: 0 }; + var changeInPosition = { x: 0, y: (oldBOTTOM - newBOTTOM) * -0.5, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; if (wantDebug) { @@ -886,13 +886,13 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - print(" originalBOTTOM:" + originalBOTTOM); + print(" oldBOTTOM:" + oldBOTTOM); print(" newBOTTOM:" + newBOTTOM); - Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } @@ -918,11 +918,11 @@ SelectionDisplay = (function () { var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); - var originalRIGHT = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; - var newRIGHT = originalRIGHT + vector.x; - var changeInDimensions = { x: (newRIGHT - originalRIGHT), y: 0 , z: 0 }; + var oldRIGHT = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; + var newRIGHT = oldRIGHT + vector.x; + var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: 0 , z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); - var changeInPosition = { x: (newRIGHT - originalRIGHT) * 0.5, y: 0, z: 0 }; + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, y: 0, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; if (wantDebug) { @@ -930,13 +930,13 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - print(" originalRIGHT:" + originalRIGHT); + print(" oldRIGHT:" + oldRIGHT); print(" newRIGHT:" + newRIGHT); - Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } @@ -962,11 +962,11 @@ SelectionDisplay = (function () { var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); - var originalLEFT = selectedEntityPropertiesOriginalPosition.x - halfDimensions.x; - var newLEFT = originalLEFT + vector.x; - var changeInDimensions = { x: (originalLEFT - newLEFT), y: 0, z: 0 }; + var oldLEFT = selectedEntityPropertiesOriginalPosition.x - halfDimensions.x; + var newLEFT = oldLEFT + vector.x; + var changeInDimensions = { x: (oldLEFT - newLEFT), y: 0, z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); - var changeInPosition = { x: (originalLEFT - newLEFT) * -0.5, y: 0, z: 0 }; + var changeInPosition = { x: (oldLEFT - newLEFT) * -0.5, y: 0, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; if (wantDebug) { @@ -974,13 +974,13 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - print(" originalLEFT:" + originalLEFT); + print(" oldLEFT:" + oldLEFT); print(" newLEFT:" + newLEFT); - Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } @@ -990,19 +990,12 @@ SelectionDisplay = (function () { Entities.editEntity(currentSelection, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); that.select(currentSelection, false); // TODO: this should be more than highlighted - }; - + }; + that.stretchRBN = function(event) { if (!entitySelected || mode !== "STRETCH_RBN") { return; // not allowed } - - var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); - var right = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; - var bottom = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; - var near = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; - var originalRBN = { x: right, y: bottom, z: near }; - pickRay = Camera.computePickRay(event.x, event.y); // translate mode left/right based on view toward entity @@ -1011,63 +1004,48 @@ SelectionDisplay = (function () { Quat.getFront(lastAvatarOrientation)); var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); - - // calculate the X/Z axis change... and use that instead of the X/Y axis change - var i = Vec3.dot(vector, Quat.getRight(orientation)); - var j = Vec3.dot(vector, Quat.getUp(orientation)); - vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i), - Vec3.multiply(Quat.getFront(orientation), j)); - - newRBN = Vec3.sum(originalRBN, vector); - var oldDimensions = selectedEntityPropertiesOriginalDimensions; - var changeInDimensions = Vec3.subtract(newRBN, originalRBN); - var newDimensions = Vec3.sum(selectedEntityProperties.dimensions, changeInDimensions); - if (newDimensions.x < 0) { - // TODO: need to handle x flip for position - newDimensions.x = Math.abs(newDimensions.x); - } + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldRIGHT = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; + var newRIGHT = oldRIGHT + vector.x; - if (newDimensions.y < 0) { - // TODO: need to handle y flip for position - newDimensions.y = Math.abs(newDimensions.y); - } + var oldBOTTOM = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; + var newBOTTOM = oldBOTTOM - vector.y; - if (newDimensions.z < 0) { - // TODO: need to handle z flip for position - newDimensions.z = Math.abs(newDimensions.z); - } - changeInDimensions = Vec3.subtract(newDimensions, oldDimensions); + var oldNEAR = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; + var newNEAR = oldNEAR - vector.z; - // TODO: need to handle registrations, for now assume center registration - var changeInPosition = Vec3.multiply(changeInDimensions, 0.5); - newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); - - var wantDebug = true; + var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newBOTTOM - oldBOTTOM) , z: (newNEAR - oldNEAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, + y: (newBOTTOM - oldBOTTOM) * -0.5, + z: (newNEAR - oldNEAR) * -0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; if (wantDebug) { print("stretchRBN... "); Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - Vec3.print(" original RBN:", originalRBN); - Vec3.print(" new RBN:", newRBN); - Vec3.print(" SEP.OriginalDimensions:", selectedEntityPropertiesOriginalDimensions); - Vec3.print(" oldDimensions:", oldDimensions); - Vec3.print(" changeInDimensions:", changeInDimensions); - Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" changeInPosition:", changeInPosition); - Vec3.print(" newPosition:", newPosition); + print(" oldRIGHT:" + oldRIGHT); + print(" newRIGHT:" + newRIGHT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); } - selectedEntityProperties.position = newPosition; selectedEntityProperties.dimensions = newDimensions; Entities.editEntity(currentSelection, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); that.select(currentSelection, false); // TODO: this should be more than highlighted }; - + that.checkMove = function() { if (currentSelection.isKnownID && (!Vec3.equal(MyAvatar.position, lastAvatarPosition) || !Quat.equal(MyAvatar.orientation, lastAvatarOrientation))){ @@ -1132,10 +1110,14 @@ SelectionDisplay = (function () { somethingClicked = true; break; case grabberRIGHT: + case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? + case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? mode = "STRETCH_RIGHT"; somethingClicked = true; break; case grabberLEFT: + case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? + case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? mode = "STRETCH_LEFT"; somethingClicked = true; break; From 98c959d94cc1d7fddfb985ec4d19aad5f124c110 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 04:21:44 -0700 Subject: [PATCH 066/104] implement more corner stretching --- examples/libraries/entitySelectionTool.js | 442 +++++++++++++++++++++- 1 file changed, 439 insertions(+), 3 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 1651fadcd4..f51b8c422b 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -345,7 +345,7 @@ SelectionDisplay = (function () { pickRay = Camera.computePickRay(event.x, event.y); lastPlaneIntersection = rayPlaneIntersection(pickRay, properties.position, Quat.getFront(lastAvatarOrientation)); - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("select() with EVENT...... "); print(" event.y:" + event.y); @@ -1045,7 +1045,385 @@ SelectionDisplay = (function () { tooltip.updateText(selectedEntityProperties); that.select(currentSelection, false); // TODO: this should be more than highlighted }; + + that.stretchLBN = function(event) { + if (!entitySelected || mode !== "STRETCH_LBN") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldLEFT = selectedEntityPropertiesOriginalPosition.x - halfDimensions.x; + var newLEFT = oldLEFT - vector.x; + + var oldBOTTOM = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; + var newBOTTOM = oldBOTTOM - vector.y; + + var oldNEAR = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; + var newNEAR = oldNEAR - vector.z; + + var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newBOTTOM - oldBOTTOM) , z: (newNEAR - oldNEAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, + y: (newBOTTOM - oldBOTTOM) * -0.5, + z: (newNEAR - oldNEAR) * -0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchLBN... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" oldLEFT:" + oldLEFT); + print(" newLEFT:" + newLEFT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchRTN = function(event) { + if (!entitySelected || mode !== "STRETCH_RTN") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldRIGHT = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; + var newRIGHT = oldRIGHT + vector.x; + + var oldTOP = selectedEntityPropertiesOriginalPosition.y + halfDimensions.y; + var newTOP = oldTOP + vector.y; + + var oldNEAR = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; + var newNEAR = oldNEAR - vector.z; + + + var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newTOP - oldTOP) , z: (newNEAR - oldNEAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, + y: (newTOP - oldTOP) * 0.5, + z: (newNEAR - oldNEAR) * -0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchRTN... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" oldRIGHT:" + oldRIGHT); + print(" newRIGHT:" + newRIGHT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchLTN = function(event) { + if (!entitySelected || mode !== "STRETCH_LTN") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldLEFT = selectedEntityPropertiesOriginalPosition.x - halfDimensions.x; + var newLEFT = oldLEFT - vector.x; + + var oldTOP = selectedEntityPropertiesOriginalPosition.y + halfDimensions.y; + var newTOP = oldTOP + vector.y; + + var oldNEAR = selectedEntityPropertiesOriginalPosition.z - halfDimensions.z; + var newNEAR = oldNEAR - vector.z; + + + var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newTOP - oldTOP) , z: (newNEAR - oldNEAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, + y: (newTOP - oldTOP) * 0.5, + z: (newNEAR - oldNEAR) * -0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchLTN... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" oldLEFT:" + oldLEFT); + print(" newLEFT:" + newLEFT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchRBF = function(event) { + if (!entitySelected || mode !== "STRETCH_RBF") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldRIGHT = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; + var newRIGHT = oldRIGHT + vector.x; + + var oldBOTTOM = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; + var newBOTTOM = oldBOTTOM - vector.y; + + var oldFAR = selectedEntityPropertiesOriginalPosition.z + halfDimensions.z; + var newFAR = oldFAR + vector.z; + + + var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newBOTTOM - oldBOTTOM) , z: (newFAR - oldFAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, + y: (newBOTTOM - oldBOTTOM) * -0.5, + z: (newFAR - oldFAR) * 0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchRBF... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" oldRIGHT:" + oldRIGHT); + print(" newRIGHT:" + newRIGHT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchLBF = function(event) { + if (!entitySelected || mode !== "STRETCH_LBF") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldLEFT = selectedEntityPropertiesOriginalPosition.x - halfDimensions.x; + var newLEFT = oldLEFT - vector.x; + + var oldBOTTOM = selectedEntityPropertiesOriginalPosition.y - halfDimensions.y; + var newBOTTOM = oldBOTTOM - vector.y; + + var oldFAR = selectedEntityPropertiesOriginalPosition.z + halfDimensions.z; + var newFAR = oldFAR + vector.z; + + + var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newBOTTOM - oldBOTTOM) , z: (newFAR - oldFAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, + y: (newBOTTOM - oldBOTTOM) * -0.5, + z: (newFAR - oldFAR) * 0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchLBF... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" oldLEFT:" + oldLEFT); + print(" newLEFT:" + newLEFT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchRTF = function(event) { + if (!entitySelected || mode !== "STRETCH_RTF") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldRIGHT = selectedEntityPropertiesOriginalPosition.x + halfDimensions.x; + var newRIGHT = oldRIGHT + vector.x; + + var oldTOP = selectedEntityPropertiesOriginalPosition.y + halfDimensions.y; + var newTOP = oldTOP + vector.y; + + var oldFAR = selectedEntityPropertiesOriginalPosition.z + halfDimensions.z; + var newFAR = oldFAR + vector.z; + + + var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newTOP - oldTOP) , z: (newFAR - oldFAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, + y: (newTOP - oldTOP) * 0.5, + z: (newFAR - oldFAR) * 0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchRTF... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" oldRIGHT:" + oldRIGHT); + print(" newRIGHT:" + newRIGHT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + + that.stretchLTF = function(event) { + if (!entitySelected || mode !== "STRETCH_LTF") { + return; // not allowed + } + pickRay = Camera.computePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, + selectedEntityPropertiesOriginalPosition, + Quat.getFront(lastAvatarOrientation)); + + var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + + var halfDimensions = Vec3.multiply(selectedEntityPropertiesOriginalDimensions, 0.5); + var oldLEFT = selectedEntityPropertiesOriginalPosition.x - halfDimensions.x; + var newLEFT = oldLEFT - vector.x; + + var oldTOP = selectedEntityPropertiesOriginalPosition.y + halfDimensions.y; + var newTOP = oldTOP + vector.y; + + var oldFAR = selectedEntityPropertiesOriginalPosition.z + halfDimensions.z; + var newFAR = oldFAR + vector.z; + + + var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newTOP - oldTOP) , z: (newFAR - oldFAR) }; + var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, + y: (newTOP - oldTOP) * 0.5, + z: (newFAR - oldFAR) * 0.5 }; + var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); + var wantDebug = false; + if (wantDebug) { + print("stretchLTF... "); + Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + print(" oldLEFT:" + oldLEFT); + print(" newLEFT:" + newLEFT); + Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + selectedEntityProperties.position = newPosition; + selectedEntityProperties.dimensions = newDimensions; + Entities.editEntity(currentSelection, selectedEntityProperties); + tooltip.updateText(selectedEntityProperties); + that.select(currentSelection, false); // TODO: this should be more than highlighted + }; + that.checkMove = function() { if (currentSelection.isKnownID && (!Vec3.equal(MyAvatar.position, lastAvatarPosition) || !Quat.equal(MyAvatar.orientation, lastAvatarOrientation))){ @@ -1066,7 +1444,7 @@ SelectionDisplay = (function () { if (result.intersects) { - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("something intersects... "); print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); @@ -1088,6 +1466,41 @@ SelectionDisplay = (function () { somethingClicked = true; break; + case grabberLBN: + mode = "STRETCH_LBN"; + somethingClicked = true; + break; + + case grabberRTN: + mode = "STRETCH_RTN"; + somethingClicked = true; + break; + + case grabberLTN: + mode = "STRETCH_LTN"; + somethingClicked = true; + break; + + case grabberRBF: + mode = "STRETCH_RBF"; + somethingClicked = true; + break; + + case grabberLBF: + mode = "STRETCH_LBF"; + somethingClicked = true; + break; + + case grabberRTF: + mode = "STRETCH_RTF"; + somethingClicked = true; + break; + + case grabberLTF: + mode = "STRETCH_LTF"; + somethingClicked = true; + break; + case grabberNEAR: case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? @@ -1181,7 +1594,7 @@ SelectionDisplay = (function () { }; that.mouseMoveEvent = function(event) { - print("mouseMoveEvent()... mode:" + mode); + //print("mouseMoveEvent()... mode:" + mode); switch (mode) { case "TRANSLATE_UP_DOWN": that.translateUpDown(event); @@ -1192,6 +1605,29 @@ SelectionDisplay = (function () { case "STRETCH_RBN": that.stretchRBN(event); break; + case "STRETCH_LBN": + that.stretchLBN(event); + break; + case "STRETCH_RTN": + that.stretchRTN(event); + break; + case "STRETCH_LTN": + that.stretchLTN(event); + break; + + case "STRETCH_RBF": + that.stretchRBF(event); + break; + case "STRETCH_LBF": + that.stretchLBF(event); + break; + case "STRETCH_RTF": + that.stretchRTF(event); + break; + case "STRETCH_LTF": + that.stretchLTF(event); + break; + case "STRETCH_NEAR": that.stretchNEAR(event); break; From f7a8689436ae77eb3e300756d42fb968d3442a77 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 04:25:13 -0700 Subject: [PATCH 067/104] hide rotate handles till implemented --- examples/libraries/entitySelectionTool.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index f51b8c422b..5a0164ad1f 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -585,9 +585,10 @@ SelectionDisplay = (function () { innerRadius: 0.9 }); - Overlays.editOverlay(yawHandle, { visible: true, position: yawCorner, rotation: yawHandleRotation}); - Overlays.editOverlay(pitchHandle, { visible: true, position: pitchCorner, rotation: pitchHandleRotation}); - Overlays.editOverlay(rollHandle, { visible: true, position: rollCorner, rotation: rollHandleRotation}); + // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden + Overlays.editOverlay(yawHandle, { visible: false, position: yawCorner, rotation: yawHandleRotation}); + Overlays.editOverlay(pitchHandle, { visible: false, position: pitchCorner, rotation: pitchHandleRotation}); + Overlays.editOverlay(rollHandle, { visible: false, position: rollCorner, rotation: rollHandleRotation}); Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; From 4ad10cb6cc5848c2413ba2d683c9e535f1c11004 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 04:27:41 -0700 Subject: [PATCH 068/104] fix comment --- examples/newEditEntities.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index c1627df7a2..de39121e35 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -5,18 +5,7 @@ // Created by Brad Hefta-Gaub on 10/2/14. // Copyright 2014 High Fidelity, Inc. // -// This script allows you to edit models either with the razor hydras or with your mouse -// -// If using the hydras : -// grab grab models with the triggers, you can then move the models around or scale them with both hands. -// You can switch mode using the bumpers so that you can move models around more easily. -// -// If using the mouse : -// - left click lets you move the model in the plane facing you. -// If pressing shift, it will move on the horizontal plane it's in. -// - right click lets you rotate the model. z and x give access to more axes of rotation while shift provides finer control. -// - left + right click lets you scale the model. -// - you can press r while holding the model to reset its rotation +// This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html From 49d081088a2727b8f6898c64a5675e0ab9835903 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 08:58:03 -0700 Subject: [PATCH 069/104] mode nodes to connected hash upon ping reply receipt --- domain-server/src/DomainServer.cpp | 28 +++++++++++++++++++++++---- libraries/networking/src/NodeList.cpp | 19 ++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b0fdb61ed5..13490b3c7b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -581,9 +581,17 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) || (isAssignment && matchingQueuedAssignment)) { // this was either not a static assignment or it was and we had a matching one in the queue + + QUuid nodeUUID; - // create a new session UUID for this node - QUuid nodeUUID = QUuid::createUuid(); + if (_connectingICEPeers.contains(packetUUID) || _connectedICEPeers.contains(packetUUID)) { + // this user negotiated a connection with us via ICE, so re-use their ICE client ID + nodeUUID = packetUUID; + } else { + // we got a packetUUID we didn't recognize, just add the node + nodeUUID = QUuid::createUuid(); + } + SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); @@ -1022,7 +1030,7 @@ void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { while (!iceResponseStream.atEnd()) { iceResponseStream >> receivedPeer; - if (!_connectingICEPeers.contains(receivedPeer.getUUID())) { + if (!_connectingICEPeers.contains(receivedPeer.getUUID()) && _connectedICEPeers.contains(receivedPeer.getUUID())) { qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; } @@ -1031,7 +1039,19 @@ void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { } void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { - qDebug() << "looking for a node with ID" << uuidFromPacketHeader(packet) << "in connecting hash"; + QUuid nodeUUID = uuidFromPacketHeader(packet); + NetworkPeer sendingPeer = _connectingICEPeers.take(nodeUUID); + + if (!sendingPeer.isNull()) { + // we had this NetworkPeer in our connecting list - add the right sock addr to our connected list + if (senderSockAddr == sendingPeer.getLocalSocket()) { + qDebug() << "Activating local socket for communication with network peer -" << sendingPeer; + _connectedICEPeers.insert(nodeUUID, sendingPeer.getLocalSocket()); + } else if (senderSockAddr == sendingPeer.getPublicSocket()) { + qDebug() << "Activating public socket for communication with network peer -" << sendingPeer; + _connectedICEPeers.insert(nodeUUID, sendingPeer.getPublicSocket()); + } + } } void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index da11684ada..de586f3d36 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -276,7 +276,7 @@ void NodeList::sendDomainServerCheckIn() { bool isUsingDTLS = false; PacketType domainPacketType = !_domainHandler.isConnected() - ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; + ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; if (!_domainHandler.isConnected()) { qDebug() << "Sending connect request to domain-server at" << _domainHandler.getHostname(); @@ -285,10 +285,17 @@ void NodeList::sendDomainServerCheckIn() { // construct the DS check in packet QUuid packetUUID = _sessionUUID; - if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) { - // this is a connect request and we're an assigned node - // so set our packetUUID as the assignment UUID - packetUUID = _domainHandler.getAssignmentUUID(); + if (domainPacketType == PacketTypeDomainConnectRequest) { + if (!_domainHandler.getAssignmentUUID().isNull()) { + // this is a connect request and we're an assigned node + // so set our packetUUID as the assignment UUID + packetUUID = _domainHandler.getAssignmentUUID(); + } else if (_domainHandler.requiresICE()) { + // this is a connect request and we're an interface client + // that used ice to discover the DS + // so send our ICE client UUID with the connect request + packetUUID = _domainHandler.getICEClientID(); + } } QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); @@ -336,7 +343,7 @@ void NodeList::handleICEConnectionToDomainServer() { qDebug() << "Sending ping packets to establish connectivity with domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getUUID()); - // send the ping packet to the local and public sockets for this node + // send the ping packet to the local and public sockets for this nodfe QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID()); writeUnverifiedDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket()); From 1a1ae43bc75e20f73da7ad0f67f8a6c6c7cd4bc9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:02:18 -0700 Subject: [PATCH 070/104] send back to the network peer on established sock addr if it exists --- domain-server/src/DomainServer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 13490b3c7b..2f74159e25 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -708,6 +708,13 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + // if we've established a connection via ICE with this peer, use that socket + // otherwise just try to reply back to them on their sending socket (although that may not work) + HifiSockAddr destinationSockAddr = _connectedICEPeers.value(node->getUUID()); + if (destinationSockAddr.isNull()) { + destinationSockAddr = senderSockAddr; + } if (nodeInterestList.size() > 0) { @@ -1811,6 +1818,10 @@ void DomainServer::nodeAdded(SharedNodePointer node) { } void DomainServer::nodeKilled(SharedNodePointer node) { + + // remove this node from the connecting / connected ICE lists (if they exist) + _connectingICEPeers.remove(node->getUUID()); + _connectedICEPeers.remove(node->getUUID()); DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); From 720f8ecae8be56f5f3261e4a048bf0e1fed87239 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:08:27 -0700 Subject: [PATCH 071/104] handle case where access_token is empty --- domain-server/src/DomainServer.cpp | 2 +- libraries/networking/src/NodeList.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2f74159e25..d3a08f7eb7 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -258,7 +258,7 @@ bool DomainServer::didSetupAccountManagerWithAccessToken() { if (accessToken.isEmpty()) { const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (accessTokenVariant->canConvert(QMetaType::QString)) { + if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { accessToken = accessTokenVariant->toString(); } else { qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present." diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index de586f3d36..7b925fa464 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -336,7 +336,7 @@ void NodeList::sendDomainServerCheckIn() { void NodeList::handleICEConnectionToDomainServer() { if (_domainHandler.getICEPeer().isNull()) { - LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), + LimitedNodeList::sendHeartbeatToIceServer(HifiSockAddr("h.dfu.co", 7337), _domainHandler.getICEClientID(), _domainHandler.getUUID()); } else { From bd68126b195988759e5357fe74d4e4dc7e5caced Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:14:23 -0700 Subject: [PATCH 072/104] fix for values without defaults --- domain-server/src/DomainServerSettingsManager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 8d866a318c..f99e0bf734 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -92,7 +92,12 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin } } - return *foundValue; + if (foundValue) { + return *foundValue; + } else { + return QVariant(); + } + } const QString SETTINGS_PATH = "/settings.json"; From 05cf0142e908561fa55d79a8a9d030c4cbf310c9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:19:51 -0700 Subject: [PATCH 073/104] fix for default value search --- domain-server/src/DomainServerSettingsManager.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index f99e0bf734..28b1151f2d 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -76,7 +76,7 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin int dotIndex = keyPath.indexOf('.'); QString groupKey = keyPath.mid(0, dotIndex); - QString settingKey = keyPath.mid(dotIndex); + QString settingKey = keyPath.mid(dotIndex + 1); foreach(const QVariant& group, _descriptionArray.toVariantList()) { QVariantMap groupMap = group.toMap(); @@ -88,16 +88,13 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin return settingMap[SETTING_DEFAULT_KEY]; } } + + return QVariant(); } } } - if (foundValue) { - return *foundValue; - } else { - return QVariant(); - } - + return QVariant(); } const QString SETTINGS_PATH = "/settings.json"; From 23f584d9da35ffc1af95a7a574b8242669dc7096 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 09:26:09 -0700 Subject: [PATCH 074/104] added file headers --- examples/libraries/ExportMenu.js | 12 +++++++++++- examples/libraries/ModelImporter.js | 11 +++++++++++ examples/libraries/ToolTip.js | 11 +++++++++++ examples/libraries/dataViewHelpers.js | 10 ++++++++++ examples/libraries/httpMultiPart.js | 11 +++++++++++ examples/libraries/modelUploader.js | 11 +++++++++++ examples/libraries/progressDialog.js | 11 +++++++++++ 7 files changed, 76 insertions(+), 1 deletion(-) diff --git a/examples/libraries/ExportMenu.js b/examples/libraries/ExportMenu.js index fb148b8150..0a47f2bd69 100644 --- a/examples/libraries/ExportMenu.js +++ b/examples/libraries/ExportMenu.js @@ -1,4 +1,14 @@ -var ExportMenu = function (opts) { +// +// ExportMenu.js +// examples/libraries +// +// 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 +// + +ExportMenu = function (opts) { var self = this; var windowDimensions = Controller.getViewportDimensions(); diff --git a/examples/libraries/ModelImporter.js b/examples/libraries/ModelImporter.js index 868d6d7d46..a0c35bb663 100644 --- a/examples/libraries/ModelImporter.js +++ b/examples/libraries/ModelImporter.js @@ -1,3 +1,14 @@ +// +// ModelImporter.js +// examples/libraries +// +// 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 +// + + ModelImporter = function (opts) { var self = this; diff --git a/examples/libraries/ToolTip.js b/examples/libraries/ToolTip.js index 0070f4676f..785a0e4d2f 100644 --- a/examples/libraries/ToolTip.js +++ b/examples/libraries/ToolTip.js @@ -1,3 +1,14 @@ +// +// ToolTip.js +// examples/libraries +// +// 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 +// + + function Tooltip() { this.x = 285; this.y = 115; diff --git a/examples/libraries/dataViewHelpers.js b/examples/libraries/dataViewHelpers.js index 6e60ba8bdc..c6b83ad1f6 100644 --- a/examples/libraries/dataViewHelpers.js +++ b/examples/libraries/dataViewHelpers.js @@ -1,3 +1,13 @@ +// +// dataViewHelpers.js +// examples/libraries +// +// 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 +// + if (typeof DataView.prototype.indexOf !== "function") { DataView.prototype.indexOf = function (searchString, position) { var searchLength = searchString.length, diff --git a/examples/libraries/httpMultiPart.js b/examples/libraries/httpMultiPart.js index 72f3bf6417..7bf3395e5e 100644 --- a/examples/libraries/httpMultiPart.js +++ b/examples/libraries/httpMultiPart.js @@ -1,3 +1,14 @@ +// +// httpMultiPart.js +// examples/libraries +// +// 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 +// + + httpMultiPart = (function () { var that = {}, parts, diff --git a/examples/libraries/modelUploader.js b/examples/libraries/modelUploader.js index fb3db0958f..d83fc8c16d 100644 --- a/examples/libraries/modelUploader.js +++ b/examples/libraries/modelUploader.js @@ -1,3 +1,14 @@ +// +// modelUploader.js +// examples/libraries +// +// 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 +// + + modelUploader = (function () { var that = {}, modelFile, diff --git a/examples/libraries/progressDialog.js b/examples/libraries/progressDialog.js index 349808131d..dff0904372 100644 --- a/examples/libraries/progressDialog.js +++ b/examples/libraries/progressDialog.js @@ -1,3 +1,14 @@ +// +// progressDialog.js +// examples/libraries +// +// 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 +// + + progressDialog = (function () { var that = {}, progressBackground, From 5be067cf11c4f6996a3435821c8913403bc3602a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:31:17 -0700 Subject: [PATCH 075/104] disable automatic networking by default --- domain-server/resources/describe-settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 303ca7805b..5521cf1660 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -17,7 +17,7 @@ "name": "automatic_networking", "label": "Automatic Networking", "help": "This defines how other nodes in the High Fidelity metaverse will be able to reach your domain-server.
If you don't want to deal with any network settings, use full automatic networking.", - "default": "full", + "default": "disabled", "type": "select", "options": [ { From c96e7f72f8ba6020cf12ed2bb7be0c87d73ec0d5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:31:38 -0700 Subject: [PATCH 076/104] use the ice server sock addr returned for the domain --- libraries/networking/src/NodeList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 7b925fa464..de586f3d36 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -336,7 +336,7 @@ void NodeList::sendDomainServerCheckIn() { void NodeList::handleICEConnectionToDomainServer() { if (_domainHandler.getICEPeer().isNull()) { - LimitedNodeList::sendHeartbeatToIceServer(HifiSockAddr("h.dfu.co", 7337), + LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), _domainHandler.getUUID()); } else { From 63024663b286c775dfcde99fe036544f41aff6b0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:37:13 -0700 Subject: [PATCH 077/104] don't require settings request for agent nodes --- libraries/networking/src/DomainHandler.cpp | 38 +++++++++++++--------- libraries/networking/src/DomainHandler.h | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index daa9b6eca6..fecbd1457f 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -185,21 +185,29 @@ void DomainHandler::setIsConnected(bool isConnected) { } } -void DomainHandler::requestDomainSettings() const { - if (_settingsObject.isEmpty()) { - // setup the URL required to grab settings JSON - QUrl settingsJSONURL; - settingsJSONURL.setScheme("http"); - settingsJSONURL.setHost(_hostname); - settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT); - settingsJSONURL.setPath("/settings.json"); - Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType()); - settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType)); - - qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString(); - - QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL)); - connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished); +void DomainHandler::requestDomainSettings() { + NodeType_t owningNodeType = NodeList::getInstance()->getOwnerType(); + if (owningNodeType == NodeType::Agent) { + // for now the agent nodes don't need any settings - this allows local assignment-clients + // to connect to a domain that is using automatic networking (since we don't have TCP hole punch yet) + _settingsObject = QJsonObject(); + emit settingsReceived(_settingsObject); + } else { + if (_settingsObject.isEmpty()) { + // setup the URL required to grab settings JSON + QUrl settingsJSONURL; + settingsJSONURL.setScheme("http"); + settingsJSONURL.setHost(_hostname); + settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT); + settingsJSONURL.setPath("/settings.json"); + Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType()); + settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType)); + + qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString(); + + QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL)); + connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished); + } } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index b41bbd98aa..515dd92c12 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -67,7 +67,7 @@ public: void setIsConnected(bool isConnected); bool hasSettings() const { return !_settingsObject.isEmpty(); } - void requestDomainSettings() const; + void requestDomainSettings(); const QJsonObject& getSettingsObject() const { return _settingsObject; } void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket); From 515789927b8067d7fb0a6dd22da251cb9150f565 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 09:43:46 -0700 Subject: [PATCH 078/104] fix float defines from CR feedback --- interface/src/entities/RenderableBoxEntityItem.cpp | 2 +- interface/src/entities/RenderableSphereEntityItem.cpp | 2 +- interface/src/ui/overlays/BillboardOverlay.cpp | 2 +- interface/src/ui/overlays/Circle3DOverlay.cpp | 2 +- interface/src/ui/overlays/Cube3DOverlay.cpp | 2 +- interface/src/ui/overlays/ImageOverlay.cpp | 2 +- interface/src/ui/overlays/Line3DOverlay.cpp | 2 +- interface/src/ui/overlays/Rectangle3DOverlay.cpp | 2 +- interface/src/ui/overlays/Sphere3DOverlay.cpp | 2 +- interface/src/ui/overlays/TextOverlay.cpp | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/entities/RenderableBoxEntityItem.cpp b/interface/src/entities/RenderableBoxEntityItem.cpp index b6363c81b1..1cc1f72e93 100644 --- a/interface/src/entities/RenderableBoxEntityItem.cpp +++ b/interface/src/entities/RenderableBoxEntityItem.cpp @@ -39,7 +39,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glm::quat rotation = getRotation(); const bool useGlutCube = true; - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; if (useGlutCube) { glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, diff --git a/interface/src/entities/RenderableSphereEntityItem.cpp b/interface/src/entities/RenderableSphereEntityItem.cpp index ccf61d3106..d561670be8 100644 --- a/interface/src/entities/RenderableSphereEntityItem.cpp +++ b/interface/src/entities/RenderableSphereEntityItem.cpp @@ -36,7 +36,7 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; glm::quat rotation = getRotation(); - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index d42fe533de..4f9e7c84f6 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -73,7 +73,7 @@ void BillboardOverlay::render() { float x = _fromImage.width() / (2.0f * maxSize); float y = -_fromImage.height() / (2.0f * maxSize); - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; xColor color = getColor(); float alpha = getAlpha(); glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 516a642639..8679e55423 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -41,7 +41,7 @@ void Circle3DOverlay::render() { //const int slices = 15; float alpha = getAlpha(); xColor color = getColor(); - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index ad936b74eb..8395fafcaa 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -39,7 +39,7 @@ void Cube3DOverlay::render() { float alpha = getAlpha(); xColor color = getColor(); - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); //glDisable(GL_LIGHTING); diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index 0bc63e423b..8322b9bea4 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -65,7 +65,7 @@ void ImageOverlay::render() { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, _textureID); } - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; xColor color = getColor(); float alpha = getAlpha(); glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index b35a22d4d8..d386ae41bf 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -37,7 +37,7 @@ void Line3DOverlay::render() { float alpha = getAlpha(); xColor color = getColor(); - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); if (getIsDashedLine()) { diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index fd41aa012c..383deb2239 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -30,7 +30,7 @@ void Rectangle3DOverlay::render() { float alpha = getAlpha(); xColor color = getColor(); - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); glDisable(GL_LIGHTING); diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 226defcf77..87650dd0fa 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -31,7 +31,7 @@ void Sphere3DOverlay::render() { const int slices = 15; float alpha = getAlpha(); xColor color = getColor(); - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index c9718e284c..89c6b1fe4f 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -54,7 +54,7 @@ void TextOverlay::render() { } - const float MAX_COLOR = 255; + const float MAX_COLOR = 255.0f; xColor backgroundColor = getBackgroundColor(); float alpha = getAlpha(); glColor4f(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR, alpha); From d9e0c91e02a278af2a3d409755a22296fe35851b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:45:40 -0700 Subject: [PATCH 079/104] remove a peer in domain-server after max connection attempts --- domain-server/src/DomainServer.cpp | 34 +++++++++++++++++------- libraries/networking/src/NetworkPeer.cpp | 6 +++-- libraries/networking/src/NetworkPeer.h | 7 +++++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d3a08f7eb7..365e335d32 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1012,17 +1012,31 @@ void DomainServer::sendHearbeatToIceServer() { void DomainServer::sendICEPingPackets() { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); - foreach(const NetworkPeer& peer, _connectingICEPeers) { - // send ping packets to this peer's interfaces - qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" - << peer.getUUID(); + QHash::iterator peer = _connectingICEPeers.begin(); + + while (peer != _connectingICEPeers.end()) { - // send the ping packet to the local and public sockets for this node - QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false); - nodeList->writeUnverifiedDatagram(localPingPacket, peer.getLocalSocket()); - - QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false); - nodeList->writeUnverifiedDatagram(publicPingPacket, peer.getPublicSocket()); + if (peer->getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) { + // we've already tried to connect to this peer enough times + // remove it from our list - if it wants to re-connect it'll come back through ice-server + peer = _connectingICEPeers.erase(peer); + } else { + // send ping packets to this peer's interfaces + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" + << peer->getUUID(); + + // send the ping packet to the local and public sockets for this node + QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false); + nodeList->writeUnverifiedDatagram(localPingPacket, peer->getLocalSocket()); + + QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false); + nodeList->writeUnverifiedDatagram(publicPingPacket, peer->getPublicSocket()); + + peer->incrementConnectionAttempts(); + + // go to next peer in hash + ++peer; + } } } diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 1dda9eb7b1..d458f579c1 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -21,7 +21,8 @@ NetworkPeer::NetworkPeer() : _publicSocket(), _localSocket(), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), - _lastHeardMicrostamp(usecTimestampNow()) + _lastHeardMicrostamp(usecTimestampNow()), + _connectionAttempts(0) { } @@ -31,7 +32,8 @@ NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, co _publicSocket(publicSocket), _localSocket(localSocket), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), - _lastHeardMicrostamp(usecTimestampNow()) + _lastHeardMicrostamp(usecTimestampNow()), + _connectionAttempts(0) { } diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 1e9b61d9f2..bb92c54eb8 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -20,6 +20,7 @@ const QString ICE_SERVER_HOSTNAME = "localhost"; const int ICE_SERVER_DEFAULT_PORT = 7337; const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000; +const int MAX_ICE_CONNECTION_ATTEMPTS = 5; class NetworkPeer : public QObject { public: @@ -50,6 +51,10 @@ public: QByteArray toByteArray() const; + int getConnectionAttempts() const { return _connectionAttempts; } + void incrementConnectionAttempts() { ++_connectionAttempts; } + void resetConnectionAttemps() { _connectionAttempts = 0; } + friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer); friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer); protected: @@ -60,6 +65,8 @@ protected: quint64 _wakeTimestamp; quint64 _lastHeardMicrostamp; + + int _connectionAttempts; private: void swap(NetworkPeer& otherPeer); }; From 55a718083820c60a26794e4249f174f838f80b21 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:47:15 -0700 Subject: [PATCH 080/104] re-send heartbeat to ICE server after max connection attempts --- libraries/networking/src/DomainHandler.h | 2 +- libraries/networking/src/NodeList.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 515dd92c12..34eda7b00d 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -59,7 +59,7 @@ public: bool requiresICE() const { return !_iceServerSockAddr.isNull(); } const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; } - const NetworkPeer& getICEPeer() const { return _icePeer; } + NetworkPeer& getICEPeer() const { return _icePeer; } void activateICELocalSocket(); void activateICEPublicSocket(); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index de586f3d36..580fdb0641 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -335,7 +335,11 @@ void NodeList::sendDomainServerCheckIn() { } void NodeList::handleICEConnectionToDomainServer() { - if (_domainHandler.getICEPeer().isNull()) { + if (_domainHandler.getICEPeer().isNull() + || _domainHandler.getICEPeer().getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) { + + _domainHandler.getICEPeer().resetConnectionAttemps(); + LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), _domainHandler.getUUID()); @@ -349,6 +353,8 @@ void NodeList::handleICEConnectionToDomainServer() { QByteArray publicPingPacket = constructPingPacket(PingType::Public, false, _domainHandler.getICEClientID()); writeUnverifiedDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket()); + + _domainHandler.getICEPeer().incrementConnectionAttempts(); } } From 467e9b1831aab6db0a2e92fd53c9572485616691 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 09:47:17 -0700 Subject: [PATCH 081/104] CR feedback --- interface/src/ui/overlays/Circle3DOverlay.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 8679e55423..01693488d4 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -33,10 +33,10 @@ void Circle3DOverlay::render() { return; // do nothing if we're not visible } - float PI_OVER_180 = 3.14159265358979f / 180.0f; - float FULL_CIRCLE = 360.0f; - float SLICES = 180.0f; // The amount of segment to create the circle - float SLICE_ANGLE = FULL_CIRCLE / SLICES; + const float PI_OVER_180 = 3.14159265358979f / 180.0f; + const float FULL_CIRCLE = 360.0f; + const float SLICES = 180.0f; // The amount of segment to create the circle + const float SLICE_ANGLE = FULL_CIRCLE / SLICES; //const int slices = 15; float alpha = getAlpha(); From 00799e39e1ee27748f65fac9deb0b265386517e2 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 09:48:54 -0700 Subject: [PATCH 082/104] CR feedback --- interface/src/ui/overlays/Base3DOverlay.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index f58dc8e29e..efa2a2bdcd 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -129,16 +129,16 @@ void Base3DOverlay::drawDashedLine(const glm::vec3& start, const glm::vec3& end) glBegin(GL_LINES); // draw each line segment with appropriate gaps - const float dashLength = 0.05f; - const float gapLength = 0.025f; - const float segmentLength = dashLength + gapLength; + const float DASH_LENGTH = 0.05f; + const float GAP_LENGTH = 0.025f; + const float SEGMENT_LENGTH = DASH_LENGTH + GAP_LENGTH; float length = glm::distance(start, end); - float segmentCount = length / segmentLength; + float segmentCount = length / SEGMENT_LENGTH; int segmentCountFloor = (int)glm::floor(segmentCount); glm::vec3 segmentVector = (end - start) / segmentCount; - glm::vec3 dashVector = segmentVector / segmentLength * dashLength; - glm::vec3 gapVector = segmentVector / segmentLength * gapLength; + glm::vec3 dashVector = segmentVector / SEGMENT_LENGTH * DASH_LENGTH; + glm::vec3 gapVector = segmentVector / SEGMENT_LENGTH * GAP_LENGTH; glm::vec3 point = start; glVertex3f(point.x, point.y, point.z); From 4ab0a8e13de130da8a476af7c274dfd48811b7b3 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 09:49:54 -0700 Subject: [PATCH 083/104] CR feedback --- interface/src/entities/RenderableModelEntityItem.cpp | 2 +- interface/src/ui/overlays/Base3DOverlay.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index d01b8222ca..e2a0933fab 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -71,7 +71,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (drawAsModel) { glPushMatrix(); { - const float alpha = getLocalRenderAlpha(); + float alpha = getLocalRenderAlpha(); if (!_model || _needsModelReload) { // TODO: this getModel() appears to be about 3% of model render time. We should optimize diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index efa2a2bdcd..89dd4d4b01 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -24,8 +24,8 @@ const bool DEFAULT_IS_DASHED_LINE = false; Base3DOverlay::Base3DOverlay() : _position(DEFAULT_POSITION), _lineWidth(DEFAULT_LINE_WIDTH), - _isSolid(DEFAULT_IS_SOLID), _rotation(), + _isSolid(DEFAULT_IS_SOLID), _isDashedLine(DEFAULT_IS_DASHED_LINE), _ignoreRayIntersection(false) { From cb6e4203925627d6cf21f47be7ad5e42690e2d3c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 09:50:58 -0700 Subject: [PATCH 084/104] fix for peer addition from ice-server --- domain-server/src/DomainServer.cpp | 2 +- libraries/networking/src/DomainHandler.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 365e335d32..67e05af2c6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1051,7 +1051,7 @@ void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { while (!iceResponseStream.atEnd()) { iceResponseStream >> receivedPeer; - if (!_connectingICEPeers.contains(receivedPeer.getUUID()) && _connectedICEPeers.contains(receivedPeer.getUUID())) { + if (!_connectingICEPeers.contains(receivedPeer.getUUID()) && !_connectedICEPeers.contains(receivedPeer.getUUID())) { qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 34eda7b00d..e9d7fb1725 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -59,7 +59,7 @@ public: bool requiresICE() const { return !_iceServerSockAddr.isNull(); } const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; } - NetworkPeer& getICEPeer() const { return _icePeer; } + NetworkPeer& getICEPeer() { return _icePeer; } void activateICELocalSocket(); void activateICEPublicSocket(); From 602404c569449f4d4396fda132d921d409281fa3 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 09:55:29 -0700 Subject: [PATCH 085/104] CR feedback --- interface/src/ui/overlays/Circle3DOverlay.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 01693488d4..cecec29130 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -33,7 +33,6 @@ void Circle3DOverlay::render() { return; // do nothing if we're not visible } - const float PI_OVER_180 = 3.14159265358979f / 180.0f; const float FULL_CIRCLE = 360.0f; const float SLICES = 180.0f; // The amount of segment to create the circle const float SLICE_ANGLE = FULL_CIRCLE / SLICES; @@ -81,7 +80,7 @@ void Circle3DOverlay::render() { glBegin(GL_QUAD_STRIP); float angle = startAt; - float angleInRadians = angle * PI_OVER_180; + float angleInRadians = glm::radians(angle); glm::vec2 firstInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); glm::vec2 firstOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); @@ -89,7 +88,7 @@ void Circle3DOverlay::render() { glVertex2f(firstOuterPoint.x, firstOuterPoint.y); while (angle < endAt) { - angleInRadians = angle * PI_OVER_180; + angleInRadians = glm::radians(angle); glm::vec2 thisInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); glm::vec2 thisOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); @@ -101,7 +100,7 @@ void Circle3DOverlay::render() { // get the last slice portion.... angle = endAt; - angleInRadians = angle * PI_OVER_180; + angleInRadians = glm::radians(angle); glm::vec2 lastInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius); glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); @@ -118,19 +117,19 @@ void Circle3DOverlay::render() { float angle = startAt; - float angleInRadians = angle * PI_OVER_180; + float angleInRadians = glm::radians(angle); glm::vec2 firstPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); glVertex2f(firstPoint.x, firstPoint.y); while (angle < endAt) { angle += SLICE_ANGLE; - angleInRadians = angle * PI_OVER_180; + angleInRadians = glm::radians(angle); glm::vec2 thisPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); glVertex2f(thisPoint.x, thisPoint.y); if (getIsDashedLine()) { angle += SLICE_ANGLE / 2.0f; // short gap - angleInRadians = angle * PI_OVER_180; + angleInRadians = glm::radians(angle); glm::vec2 dashStartPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); glVertex2f(dashStartPoint.x, dashStartPoint.y); } @@ -138,7 +137,7 @@ void Circle3DOverlay::render() { // get the last slice portion.... angle = endAt; - angleInRadians = angle * PI_OVER_180; + angleInRadians = glm::radians(angle); glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius); glVertex2f(lastOuterPoint.x, lastOuterPoint.y); glEnd(); From 46d96c22fa3c3b419c95e2c175c082af55818041 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 10:01:58 -0700 Subject: [PATCH 086/104] CR feedback --- interface/src/ui/overlays/Overlay.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index e4987e9240..c7d724e098 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -152,9 +152,7 @@ float Overlay::updatePulse() { // we can safely remove any "full" periods, since those just rotate us back // to our final glow level - while (elapsedPeriods > 1.0f) { - elapsedPeriods -= 1.0f; - } + elapsedPeriods = fmod(elapsedPeriods, 1.0f); _lastPulseUpdate = now; From f6b8a510746f53bda308ee988afd014b6991ea7f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 10:03:54 -0700 Subject: [PATCH 087/104] CR feedback --- interface/src/ui/overlays/Overlays.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 3ba3f66f1a..fe9a9edca0 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -319,6 +319,7 @@ QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, case MAX_Z_FACE: faceName = "MAX_Z_FACE"; break; + default: case UNKNOWN_FACE: faceName = "UNKNOWN_FACE"; break; @@ -345,8 +346,10 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R value.face = MAX_Y_FACE; } else if (faceName == "MIN_Z_FACE") { value.face = MIN_Z_FACE; - } else { + } else if (faceName == "MAX_Z_FACE") { value.face = MAX_Z_FACE; + } else { + value.face = UNKNOWN_FACE; }; QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { From cd6adb8f287d4cf697168350bea583748145c56c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 10:13:09 -0700 Subject: [PATCH 088/104] CR feedback --- interface/src/ui/overlays/Overlay.cpp | 40 +++++++++------------------ 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index c7d724e098..215119374e 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -155,35 +155,21 @@ float Overlay::updatePulse() { elapsedPeriods = fmod(elapsedPeriods, 1.0f); _lastPulseUpdate = now; - float glowDistance = (_pulseMax - _pulseMin); float glowDistancePerPeriod = glowDistance * 2.0f; - - // if we're currently traveling from min to max - if (_pulseDirection > 0.0f) { - float glowDelta = glowDistancePerPeriod * elapsedPeriods; - - // if by adding the glowDelta, we would pass our max, then calculate - // the distance from the max back to where we'd land... - if (_pulse + glowDelta >= _pulseMax) { - float glowDeltaToMax = (_pulse + glowDelta) - _pulseMax; - float glowDeltaFromMaxBack = glowDelta - glowDeltaToMax; - glowDelta = -glowDeltaFromMaxBack; - _pulseDirection = -1.0f; - } - _pulse += glowDelta; - } else { - float glowDelta = _pulseDirection * glowDistancePerPeriod * elapsedPeriods; - - // if by subtracting the glowDelta, we would pass our min, then calculate - // the distance from the min back to where we'd land... - if (_pulse + glowDelta <= _pulseMin) { - float glowDeltaToMin = (_pulse + glowDelta) - _pulseMin; - float glowDeltaFromMinBack = glowDelta - glowDeltaToMin; - glowDelta = -glowDeltaFromMinBack; - _pulseDirection = 1.0f; - } - _pulse += glowDelta; + + float glowDelta = _pulseDirection * glowDistancePerPeriod * elapsedPeriods; + float newGlow = _pulse + glowDelta; + float limit = (_pulseDirection > 0.0f) ? _pulseMax : _pulseMin; + float passedLimit = (_pulseDirection > 0.0f) ? (newGlow >= limit) : (newGlow <= limit); + + if (passedLimit) { + float glowDeltaToLimit = newGlow - limit; + float glowDeltaFromLimitBack = glowDelta - glowDeltaToLimit; + glowDelta = -glowDeltaFromLimitBack; + _pulseDirection *= -1.0f; } + _pulse += glowDelta; + return _pulse; } From 6846426f2300e68b9b1aeb11361476926e231f0e Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 3 Oct 2014 10:13:56 -0700 Subject: [PATCH 089/104] CR feedback --- interface/src/ui/overlays/Sphere3DOverlay.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 87650dd0fa..93eaa6e0a6 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -28,7 +28,7 @@ void Sphere3DOverlay::render() { return; // do nothing if we're not visible } - const int slices = 15; + const int SLICES = 15; float alpha = getAlpha(); xColor color = getColor(); const float MAX_COLOR = 255.0f; @@ -58,9 +58,9 @@ void Sphere3DOverlay::render() { glScalef(dimensions.x, dimensions.y, dimensions.z); //Application::getInstance()->getDeferredLightingEffect()->renderSolidCube(1.0f); if (_isSolid) { - glutSolidSphere(1.0f, slices, slices); + glutSolidSphere(1.0f, SLICES, SLICES); } else { - glutWireSphere(1.0f, slices, slices); + glutWireSphere(1.0f, SLICES, SLICES); } glPopMatrix(); glPopMatrix(); From f27105100a8e9c2c3482e93b275226836b18bbe5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 10:29:02 -0700 Subject: [PATCH 090/104] have AddressManager know its current domain and current path --- interface/src/Application.cpp | 8 ++- interface/src/Application.h | 2 + .../scripting/LocationScriptingInterface.cpp | 4 +- libraries/networking/src/AddressManager.cpp | 55 +++++++++++++++---- libraries/networking/src/AddressManager.h | 16 +++++- 5 files changed, 66 insertions(+), 19 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 51b78b6901..802648d247 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -299,6 +299,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : AddressManager& addressManager = AddressManager::getInstance(); + // use our MyAvatar position and quat for address manager path + addressManager.setPositionGetter(getPositionForPath); + addressManager.setOrientationGetter(getOrientationForPath); + // handle domain change signals from AddressManager connect(&addressManager, &AddressManager::possibleDomainChangeRequiredToHostname, this, &Application::changeDomainHostname); @@ -3458,9 +3462,7 @@ void Application::updateLocationInServer() { QJsonObject locationObject; - QString pathString = AddressManager::pathForPositionAndOrientation(_myAvatar->getPosition(), - true, - _myAvatar->getOrientation()); + QString pathString = AddressManager::getInstance().currentPath(); const QString LOCATION_KEY_IN_ROOT = "location"; const QString PATH_KEY_IN_LOCATION = "path"; diff --git a/interface/src/Application.h b/interface/src/Application.h index e50039a20e..ca26cffab8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -140,6 +140,8 @@ class Application : public QApplication { public: static Application* getInstance() { return static_cast(QCoreApplication::instance()); } static QString& resourcesPath(); + static const glm::vec3& getPositionForPath() { return getInstance()->_myAvatar->getPosition(); } + static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); } Application(int& argc, char** argv, QElapsedTimer &startup_time); ~Application(); diff --git a/interface/src/scripting/LocationScriptingInterface.cpp b/interface/src/scripting/LocationScriptingInterface.cpp index bf529fb593..047b76dab6 100644 --- a/interface/src/scripting/LocationScriptingInterface.cpp +++ b/interface/src/scripting/LocationScriptingInterface.cpp @@ -29,9 +29,7 @@ QString LocationScriptingInterface::getHref() { } QString LocationScriptingInterface::getPathname() { - MyAvatar* applicationAvatar = Application::getInstance()->getAvatar(); - return AddressManager::pathForPositionAndOrientation(applicationAvatar->getPosition(), - true, applicationAvatar->getOrientation()); + return AddressManager::getInstance().currentPath(); } QString LocationScriptingInterface::getHostname() { diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index a78e1e76a4..b1efbd3c6f 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -22,17 +22,36 @@ AddressManager& AddressManager::getInstance() { return sharedInstance; } -QString AddressManager::pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation, - const glm::quat& orientation) { +AddressManager::AddressManager() : + _currentDomain(), + _positionGetter(NULL), + _orientationGetter(NULL) +{ - QString pathString = "/" + createByteArray(position); +} + +const QString AddressManager::currentPath(bool withOrientation) const { - if (hasOrientation) { - QString orientationString = createByteArray(orientation); - pathString += "/" + orientationString; + if (_positionGetter) { + QString pathString = "/" + createByteArray(_positionGetter()); + + if (withOrientation) { + if (_orientationGetter) { + QString orientationString = createByteArray(_orientationGetter()); + pathString += "/" + orientationString; + } else { + qDebug() << "Cannot add orientation to path without a getter for position." + << "Call AdressManager::setOrientationGetter to pass a function that will return a glm::quat"; + } + + } + + return pathString; + } else { + qDebug() << "Cannot create address path without a getter for position." + << "Call AdressManager::setPositionGetter to pass a function that will return a const glm::vec3&"; + return QString(); } - - return pathString; } const JSONCallbackParameters& AddressManager::apiCallbackParameters() { @@ -134,6 +153,11 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) { emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); } + // set our current domain to the name that came back + const QString DOMAIN_NAME_KEY = "name"; + + _currentDomain = domainObject[DOMAIN_NAME_KEY].toString(); + // take the path that came back const QString LOCATION_KEY = "location"; const QString LOCATION_PATH_KEY = "path"; @@ -144,7 +168,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) { } else if (domainObject.contains(LOCATION_KEY)) { returnedPath = domainObject[LOCATION_KEY].toObject()[LOCATION_PATH_KEY].toString(); } - + bool shouldFaceViewpoint = dataObject.contains(ADDRESS_API_ONLINE_KEY); if (!returnedPath.isEmpty()) { @@ -194,16 +218,25 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) { QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive); if (hostnameRegex.indexIn(lookupString) != -1) { - emit possibleDomainChangeRequiredToHostname(hostnameRegex.cap(0)); + QString domainHostname = hostnameRegex.cap(0); + + emit possibleDomainChangeRequiredToHostname(domainHostname); emit lookupResultsFinished(); + + _currentDomain = domainHostname; + return true; } QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING); if (ipAddressRegex.indexIn(lookupString) != -1) { - emit possibleDomainChangeRequiredToHostname(ipAddressRegex.cap(0)); + QString domainIPString = ipAddressRegex.cap(0); + + emit possibleDomainChangeRequiredToHostname(domainIPString); emit lookupResultsFinished(); + + _currentDomain = domainIPString; return true; } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 128c395cb4..09fd6be55e 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -21,15 +21,21 @@ static const QString HIFI_URL_SCHEME = "hifi"; +typedef const glm::vec3& (*PositionGetter)(); +typedef glm::quat (*OrientationGetter)(); + class AddressManager : public QObject { Q_OBJECT public: static AddressManager& getInstance(); - static QString pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation = false, - const glm::quat& orientation = glm::quat()); + const QString currentPath(bool withOrientation = true) const; void attemptPlaceNameLookup(const QString& lookupString); + + void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; } + void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } + public slots: void handleLookupString(const QString& lookupString); @@ -46,6 +52,8 @@ signals: bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); private: + AddressManager(); + const JSONCallbackParameters& apiCallbackParameters(); bool handleUrl(const QUrl& lookupUrl); @@ -53,6 +61,10 @@ private: bool handleNetworkAddress(const QString& lookupString); bool handleRelativeViewpoint(const QString& pathSubsection, bool shouldFace = false); bool handleUsername(const QString& lookupString); + + QString _currentDomain; + PositionGetter _positionGetter; + OrientationGetter _orientationGetter; }; #endif // hifi_AddressManager_h \ No newline at end of file From 96aacab8ab84a0926041d7c63c5b024f37a102b2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 10:42:15 -0700 Subject: [PATCH 091/104] load Address from settings to get back to domain and path --- interface/src/Application.cpp | 13 +++++++++++-- interface/src/Menu.cpp | 2 ++ interface/src/Menu.h | 2 ++ interface/src/avatar/MyAvatar.cpp | 19 ------------------- libraries/networking/src/AddressManager.cpp | 10 ++++++++++ libraries/networking/src/AddressManager.h | 2 ++ 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 802648d247..e367fe3551 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1799,16 +1799,25 @@ void Application::init() { Menu::getInstance()->loadSettings(); _audio.setReceivedAudioStreamSettings(Menu::getInstance()->getReceivedAudioStreamSettings()); - - qDebug() << "Loaded settings"; // when --url in command line, teleport to location const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); if (urlIndex != -1) { AddressManager::getInstance().handleLookupString(arguments().value(urlIndex + 1)); + } else { + // check if we have a URL in settings to load to jump back to + // we load this separate from the other settings so we don't double lookup a URL + QSettings* interfaceSettings = lockSettings(); + QUrl addressURL = interfaceSettings->value(SETTINGS_ADDRESS_KEY).toUrl(); + + AddressManager::getInstance().handleLookupString(addressURL.toString()); + + unlockSettings(); } + qDebug() << "Loaded settings"; + #ifdef __APPLE__ if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) { // on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 94af1ad80a..5a24c48bc4 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -816,6 +816,8 @@ void Menu::saveSettings(QSettings* settings) { scanMenuBar(&saveAction, settings); Application::getInstance()->getAvatar()->saveData(settings); NodeList::getInstance()->saveData(settings); + + settings->setValue(SETTINGS_ADDRESS_KEY, AddressManager::getInstance().currentAddress()); if (lockedSettings) { Application::getInstance()->unlockSettings(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 2402090213..f73f4b02a2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -51,6 +51,8 @@ const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f; const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f; +const QString SETTINGS_ADDRESS_KEY = "address"; + enum FrustumDrawMode { FRUSTUM_DRAW_MODE_ALL, FRUSTUM_DRAW_MODE_VECTORS, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f7aa8a2bd6..aae1907b76 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -740,16 +740,8 @@ AnimationDetails MyAvatar::getAnimationDetails(const QString& url) { void MyAvatar::saveData(QSettings* settings) { settings->beginGroup("Avatar"); - settings->setValue("bodyYaw", _bodyYaw); - settings->setValue("bodyPitch", _bodyPitch); - settings->setValue("bodyRoll", _bodyRoll); - settings->setValue("headPitch", getHead()->getBasePitch()); - settings->setValue("position_x", _position.x); - settings->setValue("position_y", _position.y); - settings->setValue("position_z", _position.z); - settings->setValue("pupilDilation", getHead()->getPupilDilation()); settings->setValue("leanScale", _leanScale); @@ -800,19 +792,8 @@ void MyAvatar::saveData(QSettings* settings) { void MyAvatar::loadData(QSettings* settings) { settings->beginGroup("Avatar"); - // in case settings is corrupt or missing loadSetting() will check for NaN - _bodyYaw = loadSetting(settings, "bodyYaw", 0.0f); - _bodyPitch = loadSetting(settings, "bodyPitch", 0.0f); - _bodyRoll = loadSetting(settings, "bodyRoll", 0.0f); - getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); - glm::vec3 newPosition; - newPosition.x = loadSetting(settings, "position_x", START_LOCATION.x); - newPosition.y = loadSetting(settings, "position_y", START_LOCATION.y); - newPosition.z = loadSetting(settings, "position_z", START_LOCATION.z); - slamPosition(newPosition); - getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f)); _leanScale = loadSetting(settings, "leanScale", 0.05f); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index b1efbd3c6f..3d7617e17b 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -30,6 +30,16 @@ AddressManager::AddressManager() : } +const QUrl AddressManager::currentAddress() { + QUrl hifiURL; + + hifiURL.setScheme(HIFI_URL_SCHEME); + hifiURL.setHost(_currentDomain); + hifiURL.setPath(currentPath()); + + return hifiURL; +} + const QString AddressManager::currentPath(bool withOrientation) const { if (_positionGetter) { diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 09fd6be55e..a4618655bb 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -29,6 +29,8 @@ class AddressManager : public QObject { public: static AddressManager& getInstance(); + const QUrl currentAddress(); + const QString currentPath(bool withOrientation = true) const; void attemptPlaceNameLookup(const QString& lookupString); From ea0a1a4fb67aaca033d1ebf566fb011eee15676a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 10:46:05 -0700 Subject: [PATCH 092/104] don't store domain hostname, leverage AddressManager only --- interface/src/Application.cpp | 2 +- interface/src/Menu.cpp | 2 -- libraries/networking/src/AddressManager.h | 3 ++- libraries/networking/src/NodeList.cpp | 31 ----------------------- libraries/networking/src/NodeList.h | 3 --- 5 files changed, 3 insertions(+), 38 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e367fe3551..c25081852d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3440,7 +3440,7 @@ void Application::updateWindowTitle(){ QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; QString username = AccountManager::getInstance().getAccountInfo().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) - + nodeList->getDomainHandler().getHostname() + connectionStatus + buildVersion; + + AddressManager::getInstance().getCurrentDomain() + connectionStatus + buildVersion; AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.getAccountInfo().hasBalance()) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 5a24c48bc4..80f3b7770e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -754,7 +754,6 @@ void Menu::loadSettings(QSettings* settings) { scanMenuBar(&loadAction, settings); Application::getInstance()->getAvatar()->loadData(settings); Application::getInstance()->updateWindowTitle(); - NodeList::getInstance()->loadData(settings); // notify that a settings has changed connect(&NodeList::getInstance()->getDomainHandler(), &DomainHandler::hostnameChanged, this, &Menu::bumpSettings); @@ -815,7 +814,6 @@ void Menu::saveSettings(QSettings* settings) { scanMenuBar(&saveAction, settings); Application::getInstance()->getAvatar()->saveData(settings); - NodeList::getInstance()->saveData(settings); settings->setValue(SETTINGS_ADDRESS_KEY, AddressManager::getInstance().currentAddress()); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index a4618655bb..fe6219b3f7 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -30,9 +30,10 @@ public: static AddressManager& getInstance(); const QUrl currentAddress(); - const QString currentPath(bool withOrientation = true) const; + const QString& getCurrentDomain() const { return _currentDomain; } + void attemptPlaceNameLookup(const QString& lookupString); void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 580fdb0641..50539e1196 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -471,34 +471,3 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con sendingNode->activateSymmetricSocket(); } } - -const QString QSETTINGS_GROUP_NAME = "NodeList"; -const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname"; - -void NodeList::loadData(QSettings *settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString(); - - if (domainServerHostname.size() > 0) { - _domainHandler.setHostname(domainServerHostname); - } else { - _domainHandler.setHostname(DEFAULT_DOMAIN_HOSTNAME); - } - - settings->endGroup(); -} - -void NodeList::saveData(QSettings* settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - if (_domainHandler.getHostname() != DEFAULT_DOMAIN_HOSTNAME) { - // the user is using a different hostname, store it - settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainHandler.getHostname())); - } else { - // the user has switched back to default, remove the current setting - settings->remove(DOMAIN_SERVER_SETTING_KEY); - } - - settings->endGroup(); -} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index c1e2d12319..921f33b454 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -63,9 +63,6 @@ public: void sendAssignment(Assignment& assignment); void pingPunchForInactiveNode(const SharedNodePointer& node); - - void loadData(QSettings* settings); - void saveData(QSettings* settings); public slots: void reset(); void sendDomainServerCheckIn(); From d02795cc85658b4178dbc4cf0de84b5b878c2fce Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 10:48:09 -0700 Subject: [PATCH 093/104] have data-web respond with ice server hostname to use for connection --- domain-server/src/DomainServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 67e05af2c6..8aaf30860a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -998,7 +998,8 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, domainUpdateJSON.toUtf8()); } -const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr(QHostAddress::LocalHost, ICE_SERVER_DEFAULT_PORT); +// todo: have data-web respond with ice-server hostname to use +const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT); void DomainServer::performICEUpdates() { sendHearbeatToIceServer(); From f2d0e539a01beff87b13fc6043e2589e6681bcde Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 11:23:08 -0700 Subject: [PATCH 094/104] remove files for gh-pages that are no longer in use --- images/bg_hr.png | Bin 943 -> 0 bytes images/blacktocat.png | Bin 1428 -> 0 bytes images/body-bg.png | Bin 8859 -> 0 bytes images/highlight-bg.jpg | Bin 34222 -> 0 bytes images/hr.png | Bin 1037 -> 0 bytes images/icon_download.png | Bin 1162 -> 0 bytes images/octocat-icon.png | Bin 1651 -> 0 bytes images/sprite_download.png | Bin 16799 -> 0 bytes images/tar-gz-icon.png | Bin 1671 -> 0 bytes images/zip-icon.png | Bin 1661 -> 0 bytes index.html | 52 ------------------------------------- javascripts/main.js | 1 - javascripts/scale.fix.js | 17 ------------ params.json | 1 - 14 files changed, 71 deletions(-) delete mode 100644 images/bg_hr.png delete mode 100644 images/blacktocat.png delete mode 100644 images/body-bg.png delete mode 100644 images/highlight-bg.jpg delete mode 100644 images/hr.png delete mode 100644 images/icon_download.png delete mode 100644 images/octocat-icon.png delete mode 100644 images/sprite_download.png delete mode 100644 images/tar-gz-icon.png delete mode 100644 images/zip-icon.png delete mode 100644 index.html delete mode 100644 javascripts/main.js delete mode 100644 javascripts/scale.fix.js delete mode 100644 params.json diff --git a/images/bg_hr.png b/images/bg_hr.png deleted file mode 100644 index 7973bd69888c7e10ccad1111d555ceabb7cd99b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 943 zcmaJ=%We}f6tz@@P(=|NB#MM&xrh~+SDxc$lBSsnjl`5{8i{O>>dZLFsP%a8xMW&( zu;L&1hW-Ex*zyZ35P!e|Hi)7kr%B3!;FW!K&bjA4{C02m@!IOWRRF+RcgGC?0M`Hj zcWzt<0FYNtw*Ua^((B88K9mz8C}>4|Kw+1W5e+DbPF{SXbpTdS-0RD}{{)AeX+&YP zoTWgWdNWT+ct|B2&{3Q?=-c}b2*#0vo|S#WPup}H@0<#{f4bWXPY**oLd^!O=Qw96 zqmsa!9VHpg9W>R&NrqKeM{o*}hYq?l)%W*cn+podno$UiiUBL8W)@4;YV{Gc4AamJ zOE;~8S;a*Q8wQ*m)PQvn4RPRhX1PQ(bkJDJ6zlqAGSMbQjf;_P+P1AUEUO@*VIez7 zB*_a&wlP<5X%>n&l`&7CQX~U@EFFaAC9rfMn`E;#g`DX*Np({*R7sb$veftg4`pnD z&SXHZ{Qgro>z$-j4`{}ZMMwiW+)$}f+!mBbE_$3F&AYfa=8|V)p2GHi8TurQ6ZM|r zeIIv|Op+v|UDrW5OSL$TaH(1;m$to*XIlx@v}7DF_u?+qn{lJ1UOaCpS8u?&t_J(ppy#2$?WpTw zZ5!}v@o5q{NT6op{)5dk0Ki7qZT0fcgY%1v=8APN*jjnh`Fj5I`$g;S!;iJ--L2aP YVExC*E}?St2U03%T&bpQYW diff --git a/images/blacktocat.png b/images/blacktocat.png deleted file mode 100644 index 6e264fe57a2e35a2855405ac7d4102c3f6ddcdae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1428 zcmaJ>eNYr-99}%48<0k&n3Tp@mPo+3-M!-uSndwb`;ePBK8_PHgkiZ|V1>Qib{AME z1mhz!%0Y(|CYq)ZqH#FGus{mP8OA^xa3}*!QpCvoVxXERqh|T+59~Md&b;qC&-1*` z?_=IKlM;6MdT;Rt0KnH8XR-qTuvJ=eF8}~y5ch`w0Bjb_X+jEbOpc~o2}N2Zh}IGm;V&S(OP)q`3Q6KR$v1soLV0vC^oI%q)`bMcsT4MX4p zM99}ci>A_SNuZJ8NKman!kvf)0X3)sRYgQbM!pCt5fp(DC5$S=(MU|C#1I5r3P=xX zIU)zMn=DIjaYT*|$`b?@gW=-hVnwk^!Em`S8XX-iX(*N99FY^wm$(F640rKC%L*ov zcXAXfP>c(d6!C1PP|!ipatbuNBJ1Lp%H(8dSj1TvRUlGIi@DOQ&G!FLnqEQkf}LFT zyQVPjC}Bz1PV!74=Opc9PLT8ziy1i*7Z}dLFa^t1Ov+;fhRaL7L~>LSNmw|B1{YGss5QJq7)?=@2(zT8T-(bcmsrEg64&@x zF02Q&u+-T9GtAkIv2pz;hv3hx*a1j93$*m@XL%e!v&vm*AS+7qh zgM*05oT`AEnmo+FcxtjdEguB10c@m40T3{Jk{jOk{Zr*-t-(J1f%DlH+M5a*TSj_= zd!whiW`3|K)$hMFRMgxvNj&J9*2Z58DVR9p`Ch0WK6N%%HgS3O`s@gw_#BXV=VII6 zGmq-XvChG4Zo8d5oqkhqym0VauQ})w;N^RVo-|g}olm|U#v%@Pj8Db%j@rB12VRx$3yGb+`>E!h|z_Yj=e5*dtW#gt ziqkPyykp!&na%#Z_LNL9err>ee^_m`aISfCYt{a$XJ1&C9@v~Q7PW2f#}(hTH-tkzF3VG60%er@;UF~Z_DlqG=H-t>x>ZR(4iZ|-pRC*l!Ns(Lv8n!d`SH$^=?PqJ06LH;_$hV=9>A> z*zz~3TgD%hS}T70+&4Vw%U=)2^*rBkpy6=m#uj5{dE99)-#^kTJOICWbA9_!&@KIw NR&#>My)*0ee*yf36t@5X diff --git a/images/body-bg.png b/images/body-bg.png deleted file mode 100644 index d0618fe749178e814554f19fd158dea90020252d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8859 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 diff --git a/images/octocat-icon.png b/images/octocat-icon.png deleted file mode 100644 index f0ba137d268da4921d9f4b5905bed9e724c45770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1651 zcmaJ?Yfuwc6y8>)DuuD-ArvUfQbDF>A9*2b5`=_6tU(M>FtlnO8(3ho>+V7Vilt~P zR!5YgpjJc$#^>`CZDOFjaz97HVZkhi(&v_DdJs>*_uc2poPdL9ZKlV@m2^V zZAvIZrbqRRn#dtjidiDPIMrw^&a=kZpm-IiaN#bBp$Og#x~O~yhr5)}5HIfFa3PF9 z;7|&mr-c3ym8nky)ig_hGFT$Aq8J9srEIP>fK}CG*5FmGy|&B<=|9I0;5Cb zp%PREQ?L}14SOj;P#G@9a1_N+GzJ8x>djWtAz)*M$n|<$=iqp=!%FBhO2|bqFiG0* z1c^*6ixsw7ieX7uq9#F-lq41IGMQAX9>Hp8YXL<#_z|q_1y(*PR+t;XJ6uDsWFcYG zvNQz_`Gb?AYmtn~H;lE7u0=X37Eyr;M3{nqO~pvapwSA)=QFMXU%V2+VN*gZ6tB7w z{k9wcUOuMNBp6-2_4D7!@tf;^QlcCCO?Z_>|FmajUW0f1uE2L|dOkc1Ys$*|61!&2 zVxYq7Ea&1 zdEnyHi|t!W`;XO?1&3Yk2(pB<^^m&kO8eU5cY~mH$yo`BAF149E!w`Z_ME0U3^%KM zQyuWWRQCitG@*56)Wa;lou%b7Zv{qdkBa{wqInVIqh8CY>?zHSwuG?dwaEuRJIZHH z8W)y-#aEg9AYow3#!2FnvcS++&Rj1kyL~U>i0tm{PG6|L9}P_-<`#i10bbqFFYUYl zVBU+nyv|l|llqycgMxCv{&)qsELfI33b>{9p&20x_ z)|WH{ZSrrfiJtAyj9h<_ISMCcvA6WG2lntiuYt_e1Tr15{pIf)1 zBkPX)r03aSpB32Ujy41<>H~_9Trq*|KApR8*@{r2;*hp_Z|>lwf~ggU+e8;*Q!g+5 zZSaQfGZl?MN{-kB_fveRz0!M&$wDj)T4Y zgcYhealNOUk%gVD2eld>OECG9f6JGYzbDiav}AirgK04O6tH=KE%eoGNEW^UT~exM JZ{p&zzX3M5X(|8! diff --git a/images/sprite_download.png b/images/sprite_download.png deleted file mode 100644 index f2babd575dc1cbd6e9342cc58ca795377d35afdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16799 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 D;4+a2 diff --git a/images/tar-gz-icon.png b/images/tar-gz-icon.png deleted file mode 100644 index d50f34f6de9f468bc2c832ca126273ae53332976..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1671 zcmaJ?X;2eq7+#?TwWW%vD5biybi9!45jMz5NOM7AO{4)a#Zk$zK)^lRED+FXE2s>E zRutr{qV0H<%L~woP!CX4L>OBwSgBQ{B8~;A2Z(fnp#7nJe|_`K^E~hKKJ$L_rH5#P zUB-Ki2LQlDty1U!0GJ5?fcsb{0015jW`_X)Fr8QG`A{Z`Pc(2OAT={a5>!)$Xi`TS z%t>n+$N&IvTw)2+^LouHVQ7mAu0Evw?nBqttG?LL4S`0lpSqp&{vlv>2 zYY+`9Cu1zCWR46?)`ppq<4pc$ND>GJ*a#cNQY3ExZPaSoO4!8Eur5Jc3Hvn+fx{3! zP7J*=rPqXja)u*8Tp;u@At(xpL;}U}WNXF$9f{K(Vi7Y3oQ#CWeA-1{OvIh<&72vr?=^^Z!tadX2X7I`WO* zw+dUsl2{Vfkya+2Gm$zn%FEu0CFC4w;2AEAVOEc(7!t$qj5UT~L3yYMoP!Dl$Z&Xw z5WxkMz!$|wvJ}DyPGBg3AOwQU1;IHQgULeMxpRlbH5x)qTX_R*BGn2pWTRMt#bPF; zLL9^WFw_r|`=Y24l_{h`rP3Go!{j*b8zdj)Di~8dMbi8z*ZhWyy_0L-8+&$yg5)d- zq&bLVC~!C*!txd`B4h!M$QLO6Fq!P_US4s{@AiVc(+eC32Eg_$_|I01Ug;D;_T%dv z4+P)55|TEHAr6uR#wX-o1ptRDYK1h+)>;zYxWw&?iA7IugfDWLoQ{IZvuP7uQ@I|8 zNfq0?$GvpqGTEo8S;=R%p7Tm~rnyOXPG_O$0q2g-s%>Mkw#RIqH6AamTwZnZ&gBPm zX&>cI&Cb8vmUOhXy)ggjtupn?eA_*ccX^{7p9z?pO<9m;97T^u`h26HEu`R+WKpP zp=;Xg^>>wYrmBM+bnnSnrXR~yk-00sE>`n{AA9P*at@kRzN?C^{(Fnqsd+jkoH^sz zU;89k+T07DbQRw@+9tMm89z0C5Sx?GQYXoG&fe*#53b+fa81*C>cLWCV+wO`@wx<~ zgNP}Q?F>d%c$B9{ty)DkWi%`b)K*S-ZiDvRJ5t~sH(7WHELhYezq-4mv#_q@$*EqK z>ygVEZn{Y_XSCkv>`C*GOj$eB`@5U#G~WI0+ABL(Y#SKpOgDdI-J7R|qS^u1Xve^V zKXyxlHM!5@q2uW%TArzX>)B-;{2Yon)%~6S%iw!E6$v|3zc{DVuiV`E_(eqTx}NS0 zOPd_A^S{nQLM7fWoTYOLy^A+@w%v!b;Fe<(RbJmNe-cah76Psr4($xyHM_-9QN3_n zPw$z%aX;wyx1~yF-m3kl>!Q$8-WS|ad;UiM)S6WhdF_tz*^Q45NjEXMe1)>NNOrE` md+p82SbS+7)->?`;M7QfaY|^cnNoD${xztTT1A;GGUZ=slZcG~ diff --git a/images/zip-icon.png b/images/zip-icon.png deleted file mode 100644 index 162c425b432d8a80f54372594bbbb5ffba52b7b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1661 zcmaJ?X;2eq7+#e_smdT;;K90%2xyYsT#$u8bAUt%azqG4k&xZMikpp_g#@8mVbpqo zj*16r(W-z4h(Hw_r7BRVXdUW-5zt~QXf4IIrUgx>69ny#()ZUl-#pLrKJPQ{H{X`1 z$VC%eJY4_)n4k!k#{dA}1pvUbu}%O0{BGN$0sz2+2~{((l#VfJXdIAWR0<9%NKGmp zgKMy?HO+Vs0E}5msML&F8G&jklC800*k;lI*efW+Y|v=aaRyAmQwhBo`t?#h1QM7S ziWewhr9p>6Ayi^g;;W0s!Lm|1$`++hVd zZq(9*fgvb8XjjyvP#KIEf`&&R4ew?3#-W(B6v;7b3><_F+edmgD``+F{~t<{@6kpk z2LIsqKZT8|ECbGo!HrY~t;J(--CTPs11h6&4MWi?idsFKVpJN%P{uUM0Lo&8pbx@h z!-L_$JXpXc*?dGWlqHV?3s5eC!Y~TMz98tM)MyF4o$EU&u2iB5y^+!AwYWkqhRmda zO%NC=gaZVyEI{VZl}ZsL6p_j$ywFg-z@IA<2>4;LVXmCgW{|j^8RlXixbhLX_Pw!Z zhstr9$i%TQnj*o$d{AO!FOm`UhPc?sUZf*(Il*8M$G!#s+KS<;7ukp|Yw_%cZTsTFC2o!+ROWq2pSiNTBO;|Pu;{F} zYfuaJtfTvYDfs&R@-%QwRG+8Mxwz89@ij|biSHykvU4Ao()vb+khH1y7tBod%g|Z7 zHXLn^?Jd5QlhpsJxwckeO04X&c9c9=+Svva%mJ1c&OtU+VrX{6l*tn}i^p%2E|#={ ztk&_uk`}kt%Vn=t`xN%XI)*}St_0w>(}~#So%~rXYqY~6_q^*w^}?RJhxp;!_`d)~ zrM2LX)JU)Xvon$pdV?vuzaj&5etT}>$=gSDxBG6Xo_W0H^^51>cdFBA<^Z;e-#9(q zDPgOJ@7~1*CPjK!Z1Fu>^f0gX>FxBotG8Ay^HBLr4kGKyWhd0Cifq1Lp?g+wR>zEh zt@ZNiBVKiaqQyx!h{yzX_eT4{Bc`x2XjDdMRj0#}dnVr@D;#O7LiAQ;I zFY(2ybvBR4IRmF#>%aN2Nw`kEBFW;sqT#-4+vL#PBeu-E - - - - - Hifi by highfidelity - - - - - - - -
-
-

Hifi

-

Open, decentralized virtual worlds using sensors to control avatars and dynamically assigned devices as servers. San Francisco based startup, we are hiring: http://highfidelity.io/jobs You can also contribute by doing jobs listed at http://worklist.net -

- -

View the Project on GitHub highfidelity/hifi

- - - -
-
-

-Avatar Documentation

- - -
- -
- - - - \ No newline at end of file diff --git a/javascripts/main.js b/javascripts/main.js deleted file mode 100644 index d8135d37b1..0000000000 --- a/javascripts/main.js +++ /dev/null @@ -1 +0,0 @@ -console.log('This would be the main JS file.'); diff --git a/javascripts/scale.fix.js b/javascripts/scale.fix.js deleted file mode 100644 index 87a40ca716..0000000000 --- a/javascripts/scale.fix.js +++ /dev/null @@ -1,17 +0,0 @@ -var metas = document.getElementsByTagName('meta'); -var i; -if (navigator.userAgent.match(/iPhone/i)) { - for (i=0; i Date: Fri, 3 Oct 2014 11:23:51 -0700 Subject: [PATCH 095/104] remove stylesheets for gh-pages --- stylesheets/print.css | 226 ------------------- stylesheets/pygment_trac.css | 69 ------ stylesheets/styles.css | 255 --------------------- stylesheets/stylesheet.css | 423 ----------------------------------- 4 files changed, 973 deletions(-) delete mode 100644 stylesheets/print.css delete mode 100644 stylesheets/pygment_trac.css delete mode 100644 stylesheets/styles.css delete mode 100644 stylesheets/stylesheet.css diff --git a/stylesheets/print.css b/stylesheets/print.css deleted file mode 100644 index 541695bfd6..0000000000 --- a/stylesheets/print.css +++ /dev/null @@ -1,226 +0,0 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} -body { - font-size: 13px; - line-height: 1.5; - font-family: 'Helvetica Neue', Helvetica, Arial, serif; - color: #000; -} - -a { - color: #d5000d; - font-weight: bold; -} - -header { - padding-top: 35px; - padding-bottom: 10px; -} - -header h1 { - font-weight: bold; - letter-spacing: -1px; - font-size: 48px; - color: #303030; - line-height: 1.2; -} - -header h2 { - letter-spacing: -1px; - font-size: 24px; - color: #aaa; - font-weight: normal; - line-height: 1.3; -} -#downloads { - display: none; -} -#main_content { - padding-top: 20px; -} - -code, pre { - font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal; - color: #222; - margin-bottom: 30px; - font-size: 12px; -} - -code { - padding: 0 3px; -} - -pre { - border: solid 1px #ddd; - padding: 20px; - overflow: auto; -} -pre code { - padding: 0; -} - -ul, ol, dl { - margin-bottom: 20px; -} - - -/* COMMON STYLES */ - -table { - width: 100%; - border: 1px solid #ebebeb; -} - -th { - font-weight: 500; -} - -td { - border: 1px solid #ebebeb; - text-align: center; - font-weight: 300; -} - -form { - background: #f2f2f2; - padding: 20px; - -} - - -/* GENERAL ELEMENT TYPE STYLES */ - -h1 { - font-size: 2.8em; -} - -h2 { - font-size: 22px; - font-weight: bold; - color: #303030; - margin-bottom: 8px; -} - -h3 { - color: #d5000d; - font-size: 18px; - font-weight: bold; - margin-bottom: 8px; -} - -h4 { - font-size: 16px; - color: #303030; - font-weight: bold; -} - -h5 { - font-size: 1em; - color: #303030; -} - -h6 { - font-size: .8em; - color: #303030; -} - -p { - font-weight: 300; - margin-bottom: 20px; -} - -a { - text-decoration: none; -} - -p a { - font-weight: 400; -} - -blockquote { - font-size: 1.6em; - border-left: 10px solid #e9e9e9; - margin-bottom: 20px; - padding: 0 0 0 30px; -} - -ul li { - list-style: disc inside; - padding-left: 20px; -} - -ol li { - list-style: decimal inside; - padding-left: 3px; -} - -dl dd { - font-style: italic; - font-weight: 100; -} - -footer { - margin-top: 40px; - padding-top: 20px; - padding-bottom: 30px; - font-size: 13px; - color: #aaa; -} - -footer a { - color: #666; -} - -/* MISC */ -.clearfix:after { - clear: both; - content: '.'; - display: block; - visibility: hidden; - height: 0; -} - -.clearfix {display: inline-block;} -* html .clearfix {height: 1%;} -.clearfix {display: block;} \ No newline at end of file diff --git a/stylesheets/pygment_trac.css b/stylesheets/pygment_trac.css deleted file mode 100644 index c6a6452d24..0000000000 --- a/stylesheets/pygment_trac.css +++ /dev/null @@ -1,69 +0,0 @@ -.highlight { background: #ffffff; } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #d14 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #0086B3 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.highlight .ni { color: #800080 } /* Name.Entity */ -.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ -.highlight .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #d14 } /* Literal.String.Backtick */ -.highlight .sc { color: #d14 } /* Literal.String.Char */ -.highlight .sd { color: #d14 } /* Literal.String.Doc */ -.highlight .s2 { color: #d14 } /* Literal.String.Double */ -.highlight .se { color: #d14 } /* Literal.String.Escape */ -.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ -.highlight .si { color: #d14 } /* Literal.String.Interpol */ -.highlight .sx { color: #d14 } /* Literal.String.Other */ -.highlight .sr { color: #009926 } /* Literal.String.Regex */ -.highlight .s1 { color: #d14 } /* Literal.String.Single */ -.highlight .ss { color: #990073 } /* Literal.String.Symbol */ -.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #008080 } /* Name.Variable.Class */ -.highlight .vg { color: #008080 } /* Name.Variable.Global */ -.highlight .vi { color: #008080 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ - -.type-csharp .highlight .k { color: #0000FF } -.type-csharp .highlight .kt { color: #0000FF } -.type-csharp .highlight .nf { color: #000000; font-weight: normal } -.type-csharp .highlight .nc { color: #2B91AF } -.type-csharp .highlight .nn { color: #000000 } -.type-csharp .highlight .s { color: #A31515 } -.type-csharp .highlight .sc { color: #A31515 } diff --git a/stylesheets/styles.css b/stylesheets/styles.css deleted file mode 100644 index dacf2e1861..0000000000 --- a/stylesheets/styles.css +++ /dev/null @@ -1,255 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700); - -body { - padding:50px; - font:14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; - color:#777; - font-weight:300; -} - -h1, h2, h3, h4, h5, h6 { - color:#222; - margin:0 0 20px; -} - -p, ul, ol, table, pre, dl { - margin:0 0 20px; -} - -h1, h2, h3 { - line-height:1.1; -} - -h1 { - font-size:28px; -} - -h2 { - color:#393939; -} - -h3, h4, h5, h6 { - color:#494949; -} - -a { - color:#39c; - font-weight:400; - text-decoration:none; -} - -a small { - font-size:11px; - color:#777; - margin-top:-0.6em; - display:block; -} - -.wrapper { - width:860px; - margin:0 auto; -} - -blockquote { - border-left:1px solid #e5e5e5; - margin:0; - padding:0 0 0 20px; - font-style:italic; -} - -code, pre { - font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; - color:#333; - font-size:12px; -} - -pre { - padding:8px 15px; - background: #f8f8f8; - border-radius:5px; - border:1px solid #e5e5e5; - overflow-x: auto; -} - -table { - width:100%; - border-collapse:collapse; -} - -th, td { - text-align:left; - padding:5px 10px; - border-bottom:1px solid #e5e5e5; -} - -dt { - color:#444; - font-weight:700; -} - -th { - color:#444; -} - -img { - max-width:100%; -} - -header { - width:270px; - float:left; - position:fixed; -} - -header ul { - list-style:none; - height:40px; - - padding:0; - - background: #eee; - background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd)); - background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); - background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); - background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); - background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%); - - border-radius:5px; - border:1px solid #d2d2d2; - box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0; - width:270px; -} - -header li { - width:89px; - float:left; - border-right:1px solid #d2d2d2; - height:40px; -} - -header ul a { - line-height:1; - font-size:11px; - color:#999; - display:block; - text-align:center; - padding-top:6px; - height:40px; -} - -strong { - color:#222; - font-weight:700; -} - -header ul li + li { - width:88px; - border-left:1px solid #fff; -} - -header ul li + li + li { - border-right:none; - width:89px; -} - -header ul a strong { - font-size:14px; - display:block; - color:#222; -} - -section { - width:500px; - float:right; - padding-bottom:50px; -} - -small { - font-size:11px; -} - -hr { - border:0; - background:#e5e5e5; - height:1px; - margin:0 0 20px; -} - -footer { - width:270px; - float:left; - position:fixed; - bottom:50px; -} - -@media print, screen and (max-width: 960px) { - - div.wrapper { - width:auto; - margin:0; - } - - header, section, footer { - float:none; - position:static; - width:auto; - } - - header { - padding-right:320px; - } - - section { - border:1px solid #e5e5e5; - border-width:1px 0; - padding:20px 0; - margin:0 0 20px; - } - - header a small { - display:inline; - } - - header ul { - position:absolute; - right:50px; - top:52px; - } -} - -@media print, screen and (max-width: 720px) { - body { - word-wrap:break-word; - } - - header { - padding:0; - } - - header ul, header p.view { - position:static; - } - - pre, code { - word-wrap:normal; - } -} - -@media print, screen and (max-width: 480px) { - body { - padding:15px; - } - - header ul { - display:none; - } -} - -@media print { - body { - padding:0.4in; - font-size:12pt; - color:#444; - } -} diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css deleted file mode 100644 index 7a08b019b1..0000000000 --- a/stylesheets/stylesheet.css +++ /dev/null @@ -1,423 +0,0 @@ -/******************************************************************************* -Slate Theme for GitHub Pages -by Jason Costello, @jsncostello -*******************************************************************************/ - -@import url(pygment_trac.css); - -/******************************************************************************* -MeyerWeb Reset -*******************************************************************************/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font: inherit; - vertical-align: baseline; -} - -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} - -ol, ul { - list-style: none; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -/******************************************************************************* -Theme Styles -*******************************************************************************/ - -body { - box-sizing: border-box; - color:#373737; - background: #212121; - font-size: 16px; - font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; - line-height: 1.5; - -webkit-font-smoothing: antialiased; -} - -h1, h2, h3, h4, h5, h6 { - margin: 10px 0; - font-weight: 700; - color:#222222; - font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; - letter-spacing: -1px; -} - -h1 { - font-size: 36px; - font-weight: 700; -} - -h2 { - padding-bottom: 10px; - font-size: 32px; - background: url('../images/bg_hr.png') repeat-x bottom; -} - -h3 { - font-size: 24px; -} - -h4 { - font-size: 21px; -} - -h5 { - font-size: 18px; -} - -h6 { - font-size: 16px; -} - -p { - margin: 10px 0 15px 0; -} - -footer p { - color: #f2f2f2; -} - -a { - text-decoration: none; - color: #007edf; - text-shadow: none; - - transition: color 0.5s ease; - transition: text-shadow 0.5s ease; - -webkit-transition: color 0.5s ease; - -webkit-transition: text-shadow 0.5s ease; - -moz-transition: color 0.5s ease; - -moz-transition: text-shadow 0.5s ease; - -o-transition: color 0.5s ease; - -o-transition: text-shadow 0.5s ease; - -ms-transition: color 0.5s ease; - -ms-transition: text-shadow 0.5s ease; -} - -a:hover, a:focus {text-decoration: underline;} - -footer a { - color: #F2F2F2; - text-decoration: underline; -} - -em { - font-style: italic; -} - -strong { - font-weight: bold; -} - -img { - position: relative; - margin: 0 auto; - max-width: 739px; - padding: 5px; - margin: 10px 0 10px 0; - border: 1px solid #ebebeb; - - box-shadow: 0 0 5px #ebebeb; - -webkit-box-shadow: 0 0 5px #ebebeb; - -moz-box-shadow: 0 0 5px #ebebeb; - -o-box-shadow: 0 0 5px #ebebeb; - -ms-box-shadow: 0 0 5px #ebebeb; -} - -p img { - display: inline; - margin: 0; - padding: 0; - vertical-align: middle; - text-align: center; - border: none; -} - -pre, code { - width: 100%; - color: #222; - background-color: #fff; - - font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; - font-size: 14px; - - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; -} - -pre { - width: 100%; - padding: 10px; - box-shadow: 0 0 10px rgba(0,0,0,.1); - overflow: auto; -} - -code { - padding: 3px; - margin: 0 3px; - box-shadow: 0 0 10px rgba(0,0,0,.1); -} - -pre code { - display: block; - box-shadow: none; -} - -blockquote { - color: #666; - margin-bottom: 20px; - padding: 0 0 0 20px; - border-left: 3px solid #bbb; -} - - -ul, ol, dl { - margin-bottom: 15px -} - -ul { - list-style: inside; - padding-left: 20px; -} - -ol { - list-style: decimal inside; - padding-left: 20px; -} - -dl dt { - font-weight: bold; -} - -dl dd { - padding-left: 20px; - font-style: italic; -} - -dl p { - padding-left: 20px; - font-style: italic; -} - -hr { - height: 1px; - margin-bottom: 5px; - border: none; - background: url('../images/bg_hr.png') repeat-x center; -} - -table { - border: 1px solid #373737; - margin-bottom: 20px; - text-align: left; - } - -th { - font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; - padding: 10px; - background: #373737; - color: #fff; - } - -td { - padding: 10px; - border: 1px solid #373737; - } - -form { - background: #f2f2f2; - padding: 20px; -} - -/******************************************************************************* -Full-Width Styles -*******************************************************************************/ - -.outer { - width: 100%; -} - -.inner { - position: relative; - max-width: 640px; - padding: 20px 10px; - margin: 0 auto; -} - -#forkme_banner { - display: block; - position: absolute; - top:0; - right: 10px; - z-index: 10; - padding: 10px 50px 10px 10px; - color: #fff; - background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%; - font-weight: 700; - box-shadow: 0 0 10px rgba(0,0,0,.5); - border-bottom-left-radius: 2px; - border-bottom-right-radius: 2px; -} - -#header_wrap { - background: #212121; - background: -moz-linear-gradient(top, #373737, #212121); - background: -webkit-linear-gradient(top, #373737, #212121); - background: -ms-linear-gradient(top, #373737, #212121); - background: -o-linear-gradient(top, #373737, #212121); - background: linear-gradient(top, #373737, #212121); -} - -#header_wrap .inner { - padding: 50px 10px 30px 10px; -} - -#project_title { - margin: 0; - color: #fff; - font-size: 42px; - font-weight: 700; - text-shadow: #111 0px 0px 10px; -} - -#project_tagline { - color: #fff; - font-size: 24px; - font-weight: 300; - background: none; - text-shadow: #111 0px 0px 10px; -} - -#downloads { - position: absolute; - width: 210px; - z-index: 10; - bottom: -40px; - right: 0; - height: 70px; - background: url('../images/icon_download.png') no-repeat 0% 90%; -} - -.zip_download_link { - display: block; - float: right; - width: 90px; - height:70px; - text-indent: -5000px; - overflow: hidden; - background: url(../images/sprite_download.png) no-repeat bottom left; -} - -.tar_download_link { - display: block; - float: right; - width: 90px; - height:70px; - text-indent: -5000px; - overflow: hidden; - background: url(../images/sprite_download.png) no-repeat bottom right; - margin-left: 10px; -} - -.zip_download_link:hover { - background: url(../images/sprite_download.png) no-repeat top left; -} - -.tar_download_link:hover { - background: url(../images/sprite_download.png) no-repeat top right; -} - -#main_content_wrap { - background: #f2f2f2; - border-top: 1px solid #111; - border-bottom: 1px solid #111; -} - -#main_content { - padding-top: 40px; -} - -#footer_wrap { - background: #212121; -} - - - -/******************************************************************************* -Small Device Styles -*******************************************************************************/ - -@media screen and (max-width: 480px) { - body { - font-size:14px; - } - - #downloads { - display: none; - } - - .inner { - min-width: 320px; - max-width: 480px; - } - - #project_title { - font-size: 32px; - } - - h1 { - font-size: 28px; - } - - h2 { - font-size: 24px; - } - - h3 { - font-size: 21px; - } - - h4 { - font-size: 18px; - } - - h5 { - font-size: 14px; - } - - h6 { - font-size: 12px; - } - - code, pre { - min-width: 320px; - max-width: 480px; - font-size: 11px; - } - -} From 1b294443aca4973379c34c86fe1a8f899b67a9f1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 11:25:47 -0700 Subject: [PATCH 096/104] remove the jenkins groovy that is no longer used --- jenkins/jobs.groovy | 190 -------------------------------------------- 1 file changed, 190 deletions(-) delete mode 100644 jenkins/jobs.groovy diff --git a/jenkins/jobs.groovy b/jenkins/jobs.groovy deleted file mode 100644 index 7cab63d42d..0000000000 --- a/jenkins/jobs.groovy +++ /dev/null @@ -1,190 +0,0 @@ -JENKINS_URL = 'https://jenkins.below92.com/' -GITHUB_HOOK_URL = 'https://github.com/worklist/hifi/' -GIT_REPO_URL = 'git@github.com:worklist/hifi.git' -HIPCHAT_ROOM = 'High Fidelity' - -def hifiJob(String targetName, Boolean deploy) { - job { - name "hifi-${targetName}" - logRotator(7, -1, -1, -1) - - scm { - git(GIT_REPO_URL, 'master') { node -> - node << { - includedRegions "${targetName}/.*\nlibraries/.*" - useShallowClone true - } - } - } - - configure { project -> - project / 'properties' << { - 'com.coravy.hudson.plugins.github.GithubProjectProperty' { - projectUrl GITHUB_HOOK_URL - } - - 'jenkins.plugins.hipchat.HipChatNotifier_-HipChatJobProperty' { - room HIPCHAT_ROOM - } - - 'hudson.plugins.buildblocker.BuildBlockerProperty' { - useBuildBlocker true - blockingJobs 'hifi--seed' - } - } - - project / 'triggers' << 'com.cloudbees.jenkins.GitHubPushTrigger' { - spec '' - } - } - - configure cmakeBuild(targetName, 'make install') - - if (deploy) { - publishers { - publishScp("${ARTIFACT_DESTINATION}") { - entry("**/build/${targetName}/${targetName}", "deploy/${targetName}") - } - } - } - - configure { project -> - - project / 'publishers' << { - if (deploy) { - 'hudson.plugins.postbuildtask.PostbuildTask' { - 'tasks' { - 'hudson.plugins.postbuildtask.TaskProperties' { - logTexts { - 'hudson.plugins.postbuildtask.LogProperties' { - logText '.' - operator 'AND' - } - } - EscalateStatus true - RunIfJobSuccessful true - script "curl -d 'action=deploy&role=highfidelity-live&revision=${targetName}' https://${ARTIFACT_DESTINATION}" - } - } - } - } - - 'jenkins.plugins.hipchat.HipChatNotifier' { - jenkinsUrl JENKINS_URL - authToken "${HIPCHAT_AUTH_TOKEN}" - room HIPCHAT_ROOM - } - } - } - } -} - -static Closure cmakeBuild(srcDir, instCommand) { - return { project -> - project / 'builders' / 'hudson.plugins.cmake.CmakeBuilder' { - sourceDir '.' - buildDir 'build' - installDir '' - buildType 'RelWithDebInfo' - generator 'Unix Makefiles' - makeCommand "make ${srcDir}" - installCommand instCommand - preloadScript '' - cmakeArgs '' - projectCmakePath '/usr/local/bin/cmake' - cleanBuild 'false' - cleanInstallDir 'false' - builderImpl '' - } - } -} - -def targets = [ - 'animation-server':true, - 'assignment-server':true, - 'assignment-client':true, - 'domain-server':true, - 'eve':true, - 'pairing-server':true, - 'space-server':true, - 'voxel-server':true, -] - -/* setup all of the target jobs to use the above template */ -for (target in targets) { - queue hifiJob(target.key, target.value) -} - -/* setup the OS X interface builds */ -interfaceOSXJob = hifiJob('interface', false) -interfaceOSXJob.with { - name 'hifi-interface-osx' - - scm { - git(GIT_REPO_URL, 'stable') { node -> - node << { - includedRegions "interface/.*\nlibraries/.*" - useShallowClone true - } - } - } - - configure { project -> - project << { - assignedNode 'interface-mini' - canRoam false - } - } -} - -queue interfaceOSXJob - -/* setup the parametrized build job for builds from jenkins */ -parameterizedJob = hifiJob('$TARGET', true) -parameterizedJob.with { - name 'hifi-branch-deploy' - parameters { - stringParam('GITHUB_USER', '', "Specifies the name of the GitHub user that we're building from.") - stringParam('GIT_BRANCH', '', "Specifies the specific branch to build and deploy.") - stringParam('HOSTNAME', 'devel.highfidelity.io', "Specifies the hostname to deploy against.") - stringParam('TARGET', '', "What server to build specifically") - } - scm { - git('git@github.com:/$GITHUB_USER/hifi.git', '$GIT_BRANCH') { node -> - node << { - wipeOutWorkspace true - useShallowClone true - } - - } - } - configure { project -> - def curlCommand = 'curl -d action=hifidevgrid -d "hostname=$HOSTNAME" ' + - '-d "github_user=$GITHUB_USER" -d "build_branch=$GIT_BRANCH" ' + - "-d \"revision=\$TARGET\" https://${ARTIFACT_DESTINATION}" - - (project / publishers / 'hudson.plugins.postbuildtask.PostbuildTask' / - tasks / 'hudson.plugins.postbuildtask.TaskProperties' / script).setValue(curlCommand) - } -} - -doxygenJob = hifiJob('docs', false) -doxygenJob.with { - scm { - git(GIT_REPO_URL, 'master') { node -> - node << { - useShallowClone true - } - } - } - - configure { project -> - (project / builders).setValue('') - } - - steps { - shell('doxygen') - } -} - -queue doxygenJob From 78825f0c4d92266ef2f3f3e5b480f4b2a64772d5 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 3 Oct 2014 13:52:50 -0700 Subject: [PATCH 097/104] Fix for normalization crash. --- interface/src/MetavoxelSystem.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 1e235b6116..f82fba98a0 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -1551,6 +1551,11 @@ public: AxisIndex(int x = -1, int y = -1, int z = -1) : x(x), y(y), z(z) { } }; +static glm::vec3 safeNormalize(const glm::vec3& vector) { + float length = glm::length(vector); + return (length > 0.0f) ? (vector / length) : vector; +} + int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf) { return DEFAULT_ORDER; @@ -1879,7 +1884,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { } } } - glm::vec3 normal = glm::normalize(axisNormals[0] + axisNormals[1] + axisNormals[2]); + glm::vec3 normal = safeNormalize(axisNormals[0] + axisNormals[1] + axisNormals[2]); center /= crossingCount; // use a sequence of Givens rotations to perform a QR decomposition @@ -1967,12 +1972,12 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { vertices.append(point); } else { - axisNormals[0] = glm::normalize(axisNormals[0]); - axisNormals[1] = glm::normalize(axisNormals[1]); - axisNormals[2] = glm::normalize(axisNormals[2]); - glm::vec3 normalXY(glm::normalize(axisNormals[0] + axisNormals[1])); - glm::vec3 normalXZ(glm::normalize(axisNormals[0] + axisNormals[2])); - glm::vec3 normalYZ(glm::normalize(axisNormals[1] + axisNormals[2])); + axisNormals[0] = safeNormalize(axisNormals[0]); + axisNormals[1] = safeNormalize(axisNormals[1]); + axisNormals[2] = safeNormalize(axisNormals[2]); + glm::vec3 normalXY(safeNormalize(axisNormals[0] + axisNormals[1])); + glm::vec3 normalXZ(safeNormalize(axisNormals[0] + axisNormals[2])); + glm::vec3 normalYZ(safeNormalize(axisNormals[1] + axisNormals[2])); if (glm::dot(axisNormals[0], normalXY) > CREASE_COS_NORMAL && glm::dot(axisNormals[1], normalXY) > CREASE_COS_NORMAL) { point.setNormal(normalXY); From f673ab316a4b0d765e3483248fa74740b7ecb073 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 15:06:27 -0700 Subject: [PATCH 098/104] tell the data-server about auto networking change to full --- domain-server/src/DomainServer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c950d2f8b6..2a7c818125 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -359,6 +359,9 @@ void DomainServer::setupAutomaticNetworking() { // call our sendHeartbeaToIceServer immediately anytime a public address changes connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer); + + // tell the data server which type of automatic networking we are using + updateNetworkingInfoWithDataServer(automaticNetworkValue); } // attempt to update our sockets now From 1d3754551135bcdefff203413dc81d2238dda324 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 3 Oct 2014 15:25:03 -0700 Subject: [PATCH 099/104] Moved attenuation setting to non advanced --- domain-server/resources/describe-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 24e9a5b63b..1ee52fdf28 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -53,11 +53,11 @@ }, { "name": "attenuation_per_doubling_in_distance", - "label": "Attenuattion per doubling in distance", + "label": "Attenuation per doubling in distance", "help": "Factor between 0.0 and 1.0 (0.0: No attenuation, 1.0: extreme attenuation)", "placeholder": "0.18", "default": "0.18", - "advanced": true + "advanced": false }, { "name": "dynamic_jitter_buffer", From 4e0bb94cdd69f703d38d3ddfc5639042aef9a1b1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 15:25:47 -0700 Subject: [PATCH 100/104] fix for double ICE connect attempts from domain-server --- domain-server/src/DomainServer.cpp | 12 +++++++----- libraries/networking/src/NetworkPeer.cpp | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2a7c818125..452d9d35b7 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1027,7 +1027,7 @@ void DomainServer::sendICEPingPackets() { } else { // send ping packets to this peer's interfaces qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" - << peer->getUUID(); + << peer->getUUID(); // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false); @@ -1055,11 +1055,13 @@ void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { while (!iceResponseStream.atEnd()) { iceResponseStream >> receivedPeer; - if (!_connectingICEPeers.contains(receivedPeer.getUUID()) && !_connectedICEPeers.contains(receivedPeer.getUUID())) { - qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; + if (!_connectedICEPeers.contains(receivedPeer.getUUID())) { + if (!_connectingICEPeers.contains(receivedPeer.getUUID())) { + qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; + } + + _connectingICEPeers[receivedPeer.getUUID()] = receivedPeer; } - - _connectingICEPeers[receivedPeer.getUUID()] = receivedPeer; } } diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index d458f579c1..eaaf57471c 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -46,6 +46,7 @@ NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) { _wakeTimestamp = otherPeer._wakeTimestamp; _lastHeardMicrostamp = otherPeer._lastHeardMicrostamp; + _connectionAttempts = otherPeer._connectionAttempts; } NetworkPeer& NetworkPeer::operator=(const NetworkPeer& otherPeer) { @@ -62,6 +63,7 @@ void NetworkPeer::swap(NetworkPeer& otherPeer) { swap(_localSocket, otherPeer._localSocket); swap(_wakeTimestamp, otherPeer._wakeTimestamp); swap(_lastHeardMicrostamp, otherPeer._lastHeardMicrostamp); + swap(_connectionAttempts, otherPeer._connectionAttempts); } QByteArray NetworkPeer::toByteArray() const { From 8ce474d3e14aa36b1508c2926cdc5b732202e5cd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 15:29:16 -0700 Subject: [PATCH 101/104] make the ice server const scoped to remove warning --- 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 452d9d35b7..61310cad75 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1002,7 +1002,6 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, } // todo: have data-web respond with ice-server hostname to use -const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT); void DomainServer::performICEUpdates() { sendHearbeatToIceServer(); @@ -1010,6 +1009,7 @@ void DomainServer::performICEUpdates() { } void DomainServer::sendHearbeatToIceServer() { + const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT); LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR); } From 2ce2c9e9536624ec6f842d4e72190b73334a822f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 15:33:51 -0700 Subject: [PATCH 102/104] add a missing slash in path for release hydra support --- interface/src/devices/SixenseManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index f5d838f95b..48ea85214a 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -101,7 +101,7 @@ void SixenseManager::initialize() { _sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME); #else const QString SIXENSE_LIBRARY_NAME = "libsixense_x64"; - QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "../Frameworks/" + QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "/../Frameworks/" + SIXENSE_LIBRARY_NAME; _sixenseLibrary = new QLibrary(frameworkSixenseLibrary); From 5203113b416070f308759d6627f4a6107b9fb130 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 17:01:33 -0700 Subject: [PATCH 103/104] separate ICE ID and domain ID for reconnections to moved domains --- interface/src/Application.cpp | 6 +++--- libraries/networking/src/DomainHandler.cpp | 12 +++++++++--- libraries/networking/src/DomainHandler.h | 3 +++ libraries/networking/src/NodeList.cpp | 4 ++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d41b50a232..0551886abb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3462,9 +3462,9 @@ void Application::updateWindowTitle(){ void Application::updateLocationInServer() { AccountManager& accountManager = AccountManager::getInstance(); - const QUuid& domainUUID = NodeList::getInstance()->getDomainHandler().getUUID(); + DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler(); - if (accountManager.isLoggedIn() && !domainUUID.isNull()) { + if (accountManager.isLoggedIn() && domainHandler.isConnected() && !domainHandler.getUUID().isNull()) { // construct a QJsonObject given the user's current address information QJsonObject rootObject; @@ -3478,7 +3478,7 @@ void Application::updateLocationInServer() { const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id"; locationObject.insert(PATH_KEY_IN_LOCATION, pathString); - locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainUUID.toString()); + locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainHandler.getUUID().toString()); rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index fecbd1457f..5714e6923d 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -25,6 +25,7 @@ DomainHandler::DomainHandler(QObject* parent) : _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _assignmentUUID(), + _iceDomainID(), _iceClientID(), _iceServerSockAddr(), _icePeer(), @@ -39,9 +40,13 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); - _iceServerSockAddr = HifiSockAddr(); _icePeer = NetworkPeer(); + if (requiresICE()) { + // if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server + _sockAddr.setAddress(QHostAddress::Null); + } + _isConnected = false; emit disconnectedFromDomain(); @@ -65,6 +70,7 @@ void DomainHandler::softReset() { void DomainHandler::hardReset() { softReset(); + _iceDomainID = QUuid(); _hostname = QString(); _sockAddr.setAddress(QHostAddress::Null); } @@ -136,7 +142,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, // re-set the domain info to connect to new domain hardReset(); - setUUID(id); + _iceDomainID = id; _iceServerSockAddr = HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); // refresh our ICE client UUID to something new @@ -265,7 +271,7 @@ void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { NetworkPeer packetPeer; iceResponseStream >> packetPeer; - if (packetPeer.getUUID() != _uuid) { + if (packetPeer.getUUID() != _iceDomainID) { qDebug() << "Received a network peer with ID that does not match current domain. Will not attempt connection."; } else { qDebug() << "Received network peer object for domain -" << packetPeer; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index e9d7fb1725..86c6e6bc57 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -55,6 +55,8 @@ public: const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } + const QUuid& getICEDomainID() const { return _iceDomainID; } + const QUuid& getICEClientID() const { return _iceClientID; } bool requiresICE() const { return !_iceServerSockAddr.isNull(); } @@ -97,6 +99,7 @@ private: QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; + QUuid _iceDomainID; QUuid _iceClientID; HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 50539e1196..905fc06eeb 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -342,10 +342,10 @@ void NodeList::handleICEConnectionToDomainServer() { LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), - _domainHandler.getUUID()); + _domainHandler.getICEDomainID()); } else { qDebug() << "Sending ping packets to establish connectivity with domain-server with ID" - << uuidStringWithoutCurlyBraces(_domainHandler.getUUID()); + << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); // send the ping packet to the local and public sockets for this nodfe QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID()); From aa15ef7f85c71774ac2db891148c332180e2cc9a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 3 Oct 2014 17:10:56 -0700 Subject: [PATCH 104/104] only have the ice-server respond if there was a matching conectee --- ice-server/src/IceServer.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index e3db679009..c06bb0fc88 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -102,7 +102,7 @@ void IceServer::processDatagrams() { if (requestingConnections.size() > 0) { // send a heartbeart response based on the set of connections qDebug() << "Sending a heartbeat response to" << senderUUID << "who has" << requestingConnections.size() - << "potential connections"; + << "potential connections"; sendHeartbeatResponse(sendingSockAddr, requestingConnections); } } @@ -114,6 +114,7 @@ void IceServer::sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, Q QByteArray outgoingPacket(MAX_PACKET_SIZE, 0); int currentPacketSize = populatePacketHeader(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id); + int numHeaderBytes = currentPacketSize; // go through the connections, sending packets containing connection information for those nodes while (peerID != connections.end()) { @@ -142,9 +143,11 @@ void IceServer::sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, Q } } - // write the last packet - _serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize, - destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + if (currentPacketSize > numHeaderBytes) { + // write the last packet, if there is data in it + _serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize, + destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + } } void IceServer::clearInactivePeers() {