diff --git a/interface/resources/shaders/metavoxel_heightfield_cursor.frag b/interface/resources/shaders/metavoxel_heightfield_cursor.frag new file mode 100644 index 0000000000..0bb5e21e7d --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_cursor.frag @@ -0,0 +1,32 @@ +#version 120 + +// +// metavoxel_heightfield_cursor.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/7/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the inner radius of the outline, squared +const float SQUARED_OUTLINE_INNER_RADIUS = 0.81; + +// the outer radius of the outline, squared +const float SQUARED_OUTLINE_OUTER_RADIUS = 1.0; + +// the inner radius of the inset, squared +const float SQUARED_INSET_INNER_RADIUS = 0.855625; + +// the outer radius of the inset, squared +const float SQUARED_INSET_OUTER_RADIUS = 0.950625; + +void main(void) { + // use the distance to compute the ring color, then multiply it by the varying color + float squaredDistance = dot(gl_TexCoord[0].st, gl_TexCoord[0].st); + float alpha = step(SQUARED_OUTLINE_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_OUTLINE_OUTER_RADIUS); + float white = step(SQUARED_INSET_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_INSET_OUTER_RADIUS); + gl_FragColor = gl_Color * vec4(white, white, white, alpha); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_cursor.vert b/interface/resources/shaders/metavoxel_heightfield_cursor.vert new file mode 100644 index 0000000000..20502fbdce --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_cursor.vert @@ -0,0 +1,31 @@ +#version 120 + +// +// metavoxel_heighfield_cursor.vert +// vertex shader +// +// Created by Andrzej Kapolka on 8/7/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the height texture +uniform sampler2D heightMap; + +// the distance between height points in texture space +uniform float heightScale; + +void main(void) { + // compute the view space coordinates + float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; + vec4 viewPosition = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + gl_Position = gl_ProjectionMatrix * viewPosition; + + // generate the texture coordinates from the view position + gl_TexCoord[0] = vec4(dot(viewPosition, gl_EyePlaneS[4]), dot(viewPosition, gl_EyePlaneT[4]), 0.0, 1.0); + + // the zero height should be invisible + gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0 - step(height, 0.0)); +} diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index f66d71070a..597542778a 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -118,6 +118,73 @@ void MetavoxelSystem::render() { guideToAugmented(renderVisitor); } +class HeightfieldCursorRenderVisitor : public MetavoxelVisitor { +public: + + HeightfieldCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds); + + virtual int visit(MetavoxelInfo& info); + +private: + + Box _bounds; +}; + +HeightfieldCursorRenderVisitor::HeightfieldCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds) : + MetavoxelVisitor(QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector(), lod), + _bounds(bounds) { +} + +int HeightfieldCursorRenderVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + BufferDataPointer buffer = info.inputValues.at(0).getInlineValue(); + if (buffer) { + buffer->render(true); + } + return STOP_RECURSION; +} + +void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float radius) { + glDepthFunc(GL_LEQUAL); + glEnable(GL_CULL_FACE); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, -1.0f); + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + DefaultMetavoxelRendererImplementation::getHeightfieldCursorProgram().bind(); + + glActiveTexture(GL_TEXTURE4); + float scale = 1.0f / radius; + glm::vec4 sCoefficients(scale, 0.0f, 0.0f, -scale * position.x); + glm::vec4 tCoefficients(0.0f, 0.0f, scale, -scale * position.z); + glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&sCoefficients); + glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&tCoefficients); + glActiveTexture(GL_TEXTURE0); + + glm::vec3 extents(radius, radius, radius); + HeightfieldCursorRenderVisitor visitor(getLOD(), Box(position - extents, position + extents)); + guideToAugmented(visitor); + + DefaultMetavoxelRendererImplementation::getHeightfieldCursorProgram().release(); + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_CULL_FACE); + glDepthFunc(GL_LESS); +} + void MetavoxelSystem::deleteTextures(int heightID, int colorID) { glDeleteTextures(1, (GLuint*)&heightID); glDeleteTextures(1, (GLuint*)&colorID); @@ -239,7 +306,7 @@ PointBuffer::PointBuffer(const BufferPointVector& points) : _points(points) { } -void PointBuffer::render() { +void PointBuffer::render(bool cursor) { // initialize buffer, etc. on first render if (!_buffer.isCreated()) { _buffer.setUsagePattern(QOpenGLBuffer::StaticDraw); @@ -294,7 +361,7 @@ public: glm::vec3 vertex; }; -void HeightfieldBuffer::render() { +void HeightfieldBuffer::render(bool cursor) { // initialize textures, etc. on first render if (_heightTextureID == 0) { glGenTextures(1, &_heightTextureID); @@ -385,17 +452,24 @@ void HeightfieldBuffer::render() { glBindTexture(GL_TEXTURE_2D, _heightTextureID); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, _colorTextureID); + int heightScaleLocation; + if (cursor) { + heightScaleLocation = DefaultMetavoxelRendererImplementation::getCursorHeightScaleLocation(); + } else { + heightScaleLocation = DefaultMetavoxelRendererImplementation::getHeightScaleLocation(); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, _colorTextureID); + } - DefaultMetavoxelRendererImplementation::getHeightfieldProgram().setUniformValue( - DefaultMetavoxelRendererImplementation::getHeightScaleLocation(), 1.0f / _heightSize); + DefaultMetavoxelRendererImplementation::getHeightfieldProgram().setUniformValue(heightScaleLocation, 1.0f / _heightSize); glDrawRangeElements(GL_QUADS, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); - glBindTexture(GL_TEXTURE_2D, 0); + if (!cursor) { + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + } - glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); glPopMatrix(); @@ -475,6 +549,17 @@ void DefaultMetavoxelRendererImplementation::init() { _heightfieldProgram.setUniformValue("diffuseMap", 1); _heightScaleLocation = _heightfieldProgram.uniformLocation("heightScale"); _heightfieldProgram.release(); + + _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_cursor.vert"); + _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_cursor.frag"); + _heightfieldCursorProgram.link(); + + _heightfieldCursorProgram.bind(); + _heightfieldCursorProgram.setUniformValue("heightMap", 0); + _cursorHeightScaleLocation = _heightfieldCursorProgram.uniformLocation("heightScale"); + _heightfieldCursorProgram.release(); } } @@ -771,6 +856,8 @@ ProgramObject DefaultMetavoxelRendererImplementation::_pointProgram; int DefaultMetavoxelRendererImplementation::_pointScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldProgram; int DefaultMetavoxelRendererImplementation::_heightScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldCursorProgram; +int DefaultMetavoxelRendererImplementation::_cursorHeightScaleLocation; static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { GLdouble coefficients[] = { x, y, z, w }; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index e7ee0d2c18..7b681ff3a9 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -43,6 +43,8 @@ public: void simulate(float deltaTime); void render(); + void renderHeightfieldCursor(const glm::vec3& position, float radius); + Q_INVOKABLE void deleteTextures(int heightID, int colorID); protected: @@ -105,7 +107,7 @@ public: virtual ~BufferData(); - virtual void render() = 0; + virtual void render(bool cursor = false) = 0; }; typedef QExplicitlySharedDataPointer BufferDataPointer; @@ -116,7 +118,7 @@ public: PointBuffer(const BufferPointVector& points); - virtual void render(); + virtual void render(bool cursor = false); private: @@ -140,7 +142,7 @@ public: const QByteArray& getHeight() const { return _height; } const QByteArray& getColor() const { return _color; } - virtual void render(); + virtual void render(bool cursor = false); private: @@ -193,6 +195,9 @@ public: static ProgramObject& getHeightfieldProgram() { return _heightfieldProgram; } static int getHeightScaleLocation() { return _heightScaleLocation; } + static ProgramObject& getHeightfieldCursorProgram() { return _heightfieldCursorProgram; } + static int getCursorHeightScaleLocation() { return _cursorHeightScaleLocation; } + Q_INVOKABLE DefaultMetavoxelRendererImplementation(); virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod); @@ -206,6 +211,9 @@ private: static ProgramObject _heightfieldProgram; static int _heightScaleLocation; + + static ProgramObject _heightfieldCursorProgram; + static int _cursorHeightScaleLocation; }; /// Base class for spanner renderers; provides clipping. diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 096798e77e..d35ed93f1b 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -118,6 +118,8 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new SetSpannerTool(this)); addTool(new ImportHeightfieldTool(this)); addTool(new EraseHeightfieldTool(this)); + addTool(new HeightfieldHeightBrushTool(this)); + addTool(new HeightfieldColorBrushTool(this)); updateAttributes(); @@ -1080,3 +1082,72 @@ void EraseHeightfieldTool::apply() { message.edit = QVariant::fromValue(edit); Application::getInstance()->getMetavoxels()->applyEdit(message, true); } + +HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QString& name) : + MetavoxelTool(editor, name, false) { + + QWidget* widget = new QWidget(); + widget->setLayout(_form = new QFormLayout()); + layout()->addWidget(widget); + + _form->addRow("Radius:", _radius = new QDoubleSpinBox()); + _radius->setSingleStep(0.01); + _radius->setMaximum(FLT_MAX); + _radius->setValue(1.0); +} + +void HeightfieldBrushTool::render() { + if (Application::getInstance()->isMouseHidden()) { + return; + } + + // find the intersection with the heightfield + glm::vec3 origin = Application::getInstance()->getMouseRayOrigin(); + glm::vec3 direction = Application::getInstance()->getMouseRayDirection(); + + float distance; + if (!Application::getInstance()->getMetavoxels()->findFirstRayHeightfieldIntersection(origin, direction, distance)) { + return; + } + Application::getInstance()->getMetavoxels()->renderHeightfieldCursor( + _position = origin + distance * direction, _radius->value()); +} + +bool HeightfieldBrushTool::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) { + MetavoxelEditMessage message = { createEdit(static_cast(event)->button() == Qt::RightButton) }; + Application::getInstance()->getMetavoxels()->applyEdit(message, true); + return true; + } + return false; +} + +HeightfieldHeightBrushTool::HeightfieldHeightBrushTool(MetavoxelEditor* editor) : + HeightfieldBrushTool(editor, "Height Brush") { + + _form->addRow("Height:", _height = new QDoubleSpinBox()); + _height->setMinimum(-FLT_MAX); + _height->setMaximum(FLT_MAX); + _height->setValue(1.0); +} + +QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) { + return QVariant::fromValue(PaintHeightfieldHeightEdit(_position, _radius->value(), + 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(PaintHeightfieldColorEdit(_position, _radius->value(), _color->getColor())); +} diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index cc30896c49..87d95a6927 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -18,6 +18,7 @@ #include "MetavoxelSystem.h" #include "renderer/ProgramObject.h" +class QColorEditor; class QComboBox; class QDoubleSpinBox; class QGroupBox; @@ -282,7 +283,7 @@ private: HeightfieldPreview _preview; }; -// Allows clearing heighfield blocks. +/// Allows clearing heighfield blocks. class EraseHeightfieldTool : public HeightfieldTool { Q_OBJECT @@ -302,4 +303,60 @@ private: QSpinBox* _length; }; +/// Base class for tools that allow painting on heightfields. +class HeightfieldBrushTool : public MetavoxelTool { + Q_OBJECT + +public: + + HeightfieldBrushTool(MetavoxelEditor* editor, const QString& name); + + virtual void render(); + + virtual bool eventFilter(QObject* watched, QEvent* event); + +protected: + + virtual QVariant createEdit(bool alternate) = 0; + + QFormLayout* _form; + QDoubleSpinBox* _radius; + + glm::vec3 _position; +}; + +/// Allows raising or lowering parts of the heightfield. +class HeightfieldHeightBrushTool : public HeightfieldBrushTool { + Q_OBJECT + +public: + + HeightfieldHeightBrushTool(MetavoxelEditor* editor); + +protected: + + virtual QVariant createEdit(bool alternate); + +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; +}; + #endif // hifi_MetavoxelEditor_h diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index a2d3410314..70b574d2a2 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -61,6 +61,180 @@ SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(cons return closestSpanner; } +class RayHeightfieldIntersectionVisitor : public RayIntersectionVisitor { +public: + + float intersectionDistance; + + RayHeightfieldIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, const MetavoxelLOD& lod); + + virtual int visit(MetavoxelInfo& info, float distance); +}; + +RayHeightfieldIntersectionVisitor::RayHeightfieldIntersectionVisitor(const glm::vec3& origin, + const glm::vec3& direction, const MetavoxelLOD& lod) : + RayIntersectionVisitor(origin, direction, QVector() << + AttributeRegistry::getInstance()->getHeightfieldAttribute(), QVector(), lod), + intersectionDistance(FLT_MAX) { +} + +static const float EIGHT_BIT_MAXIMUM_RECIPROCAL = 1.0f / 255.0f; + +int RayHeightfieldIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { + if (!info.isLeaf) { + return _order; + } + HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); + if (!pointer) { + return STOP_RECURSION; + } + const QByteArray& contents = pointer->getContents(); + const uchar* src = (const uchar*)contents.constData(); + int size = glm::sqrt((float)contents.size()); + int highest = size - 1; + float heightScale = highest * EIGHT_BIT_MAXIMUM_RECIPROCAL; + + // find the initial location in heightfield coordinates + glm::vec3 entry = (_origin + distance * _direction - info.minimum) * (float)highest / info.size; + glm::vec3 floors = glm::floor(entry); + glm::vec3 ceils = glm::ceil(entry); + if (floors.x == ceils.x) { + if (_direction.x > 0.0f) { + ceils.x += 1.0f; + } else { + floors.x -= 1.0f; + } + } + if (floors.z == ceils.z) { + if (_direction.z > 0.0f) { + ceils.z += 1.0f; + } else { + floors.z -= 1.0f; + } + } + + bool withinBounds = true; + float accumulatedDistance = 0.0f; + while (withinBounds) { + // find the heights at the corners of the current cell + int floorX = qMin(qMax((int)floors.x, 0), highest); + int floorZ = qMin(qMax((int)floors.z, 0), highest); + int ceilX = qMin(qMax((int)ceils.x, 0), highest); + int ceilZ = qMin(qMax((int)ceils.z, 0), highest); + float upperLeft = src[floorZ * size + floorX] * heightScale; + float upperRight = src[floorZ * size + ceilX] * heightScale; + float lowerLeft = src[ceilZ * size + floorX] * heightScale; + float lowerRight = src[ceilZ * size + ceilX] * heightScale; + + // find the distance to the next x coordinate + float xDistance = FLT_MAX; + if (_direction.x > 0.0f) { + xDistance = (ceils.x - entry.x) / _direction.x; + } else if (_direction.x < 0.0f) { + xDistance = (floors.x - entry.x) / _direction.x; + } + + // and the distance to the next z coordinate + float zDistance = FLT_MAX; + if (_direction.z > 0.0f) { + zDistance = (ceils.z - entry.z) / _direction.z; + } else if (_direction.z < 0.0f) { + zDistance = (floors.z - entry.z) / _direction.z; + } + + // the exit distance is the lower of those two + float exitDistance = qMin(xDistance, zDistance); + glm::vec3 exit, nextFloors = floors, nextCeils = ceils; + if (exitDistance == FLT_MAX) { + if (_direction.y > 0.0f) { + return SHORT_CIRCUIT; // line points upwards; no collisions possible + } + withinBounds = false; // line points downwards; check this cell only + + } else { + // find the exit point and the next cell, and determine whether it's still within the bounds + exit = entry + exitDistance * _direction; + withinBounds = (exit.y >= 0.0f && exit.y <= highest); + if (exitDistance == xDistance) { + if (_direction.x > 0.0f) { + nextFloors.x += 1.0f; + withinBounds &= (nextCeils.x += 1.0f) <= highest; + } else { + withinBounds &= (nextFloors.x -= 1.0f) >= 0.0f; + nextCeils.x -= 1.0f; + } + } + if (exitDistance == zDistance) { + if (_direction.z > 0.0f) { + nextFloors.z += 1.0f; + withinBounds &= (nextCeils.z += 1.0f) <= highest; + } else { + withinBounds &= (nextFloors.z -= 1.0f) >= 0.0f; + nextCeils.z -= 1.0f; + } + } + // check the vertical range of the ray against the ranges of the cell heights + if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) || + qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) { + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + continue; + } + } + // having passed the bounds check, we must check against the planes + glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z); + + // first check the triangle including the Z+ segment + glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft); + float lowerProduct = glm::dot(lowerNormal, _direction); + if (lowerProduct < 0.0f) { + float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct; + glm::vec3 intersection = relativeEntry + planeDistance * _direction; + if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && + intersection.z >= intersection.x) { + intersectionDistance = qMin(intersectionDistance, distance + + (accumulatedDistance + planeDistance) * (info.size / highest)); + return SHORT_CIRCUIT; + } + } + + // then the one with the X+ segment + glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight); + float upperProduct = glm::dot(upperNormal, _direction); + if (upperProduct < 0.0f) { + float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct; + glm::vec3 intersection = relativeEntry + planeDistance * _direction; + if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && + intersection.x >= intersection.z) { + intersectionDistance = qMin(intersectionDistance, distance + + (accumulatedDistance + planeDistance) * (info.size / highest)); + return SHORT_CIRCUIT; + } + } + + // no joy; continue on our way + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + } + + return STOP_RECURSION; +} + +bool MetavoxelClientManager::findFirstRayHeightfieldIntersection(const glm::vec3& origin, + const glm::vec3& direction, float& distance) { + RayHeightfieldIntersectionVisitor visitor(origin, direction, getLOD()); + guide(visitor); + if (visitor.intersectionDistance == FLT_MAX) { + return false; + } + distance = visitor.intersectionDistance; + return true; +} + void MetavoxelClientManager::setSphere(const glm::vec3& center, float radius, const QColor& color) { Sphere* sphere = new Sphere(); sphere->setTranslation(center); @@ -78,6 +252,84 @@ void MetavoxelClientManager::applyEdit(const MetavoxelEditMessage& edit, bool re QMetaObject::invokeMethod(_updater, "applyEdit", Q_ARG(const MetavoxelEditMessage&, edit), Q_ARG(bool, reliable)); } +class HeightfieldHeightVisitor : public MetavoxelVisitor { +public: + + float height; + + HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location); + + virtual int visit(MetavoxelInfo& info); + +private: + + glm::vec3 _location; +}; + +HeightfieldHeightVisitor::HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute(), + QVector(), lod), + height(-FLT_MAX), + _location(location) { +} + +static const int REVERSE_ORDER = MetavoxelVisitor::encodeOrder(7, 6, 5, 4, 3, 2, 1, 0); + +int HeightfieldHeightVisitor::visit(MetavoxelInfo& info) { + glm::vec3 relative = _location - info.minimum; + if (relative.x < 0.0f || relative.z < 0.0f || relative.x > info.size || relative.z > info.size || + height >= info.minimum.y + info.size) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return REVERSE_ORDER; + } + HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); + if (!pointer) { + return STOP_RECURSION; + } + const QByteArray& contents = pointer->getContents(); + const uchar* src = (const uchar*)contents.constData(); + int size = glm::sqrt((float)contents.size()); + int highest = size - 1; + relative *= highest / info.size; + + // find the bounds of the cell containing the point and the shared vertex heights + glm::vec3 floors = glm::floor(relative); + glm::vec3 ceils = glm::ceil(relative); + glm::vec3 fracts = glm::fract(relative); + int floorX = qMin(qMax((int)floors.x, 0), highest); + int floorZ = qMin(qMax((int)floors.z, 0), highest); + int ceilX = qMin(qMax((int)ceils.x, 0), highest); + int ceilZ = qMin(qMax((int)ceils.z, 0), highest); + float upperLeft = src[floorZ * size + floorX]; + float lowerRight = src[ceilZ * size + ceilX]; + float interpolatedHeight; + + // the final vertex (and thus which triangle we check) depends on which half we're on + if (fracts.x > fracts.z) { + float upperRight = src[floorZ * size + ceilX]; + interpolatedHeight = glm::mix(glm::mix(upperLeft, upperRight, fracts.x), lowerRight, fracts.z); + + } else { + float lowerLeft = src[ceilZ * size + floorX]; + interpolatedHeight = glm::mix(upperLeft, glm::mix(lowerLeft, lowerRight, fracts.x), fracts.z); + } + if (interpolatedHeight == 0.0f) { + return STOP_RECURSION; // ignore zero values + } + + // convert the interpolated height into world space + height = qMax(height, info.minimum.y + interpolatedHeight * info.size * EIGHT_BIT_MAXIMUM_RECIPROCAL); + return SHORT_CIRCUIT; +} + +float MetavoxelClientManager::getHeightfieldHeight(const glm::vec3& location) { + HeightfieldHeightVisitor visitor(getLOD(), location); + guide(visitor); + return visitor.height; +} + MetavoxelLOD MetavoxelClientManager::getLOD() { return MetavoxelLOD(); } diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index 333b709b9e..8fc9bcd38d 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -37,12 +37,16 @@ public: SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, float& distance); + bool findFirstRayHeightfieldIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance); + Q_INVOKABLE void setSphere(const glm::vec3& center, float radius, const QColor& color = QColor(Qt::gray)); Q_INVOKABLE void setSpanner(const SharedObjectPointer& object, bool reliable = false); Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false); + Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location); + /// Returns the current LOD. This must be thread-safe, as it will be called from the updater thread. virtual MetavoxelLOD getLOD(); diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index dba9bc9c5c..926d839991 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -318,3 +318,169 @@ SetDataEdit::SetDataEdit(const glm::vec3& minimum, const MetavoxelData& data, bo void SetDataEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { data.set(minimum, this->data, blend); } + +PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius, float height) : + position(position), + radius(radius), + height(height) { +} + +class PaintHeightfieldHeightEditVisitor : public MetavoxelVisitor { +public: + + PaintHeightfieldHeightEditVisitor(const PaintHeightfieldHeightEdit& edit); + + virtual int visit(MetavoxelInfo& info); + +private: + + PaintHeightfieldHeightEdit _edit; + Box _bounds; +}; + +PaintHeightfieldHeightEditVisitor::PaintHeightfieldHeightEditVisitor(const PaintHeightfieldHeightEdit& edit) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute(), + QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute()), + _edit(edit) { + + glm::vec3 extents(_edit.radius, _edit.radius, _edit.radius); + _bounds = Box(_edit.position - extents, _edit.position + extents); +} + +int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); + if (!pointer) { + return STOP_RECURSION; + } + QByteArray contents(pointer->getContents()); + int size = glm::sqrt((float)contents.size()); + int highest = size - 1; + float heightScale = highest / info.size; + + glm::vec3 center = (_edit.position - info.minimum) * heightScale; + float scaledRadius = _edit.radius * heightScale; + glm::vec3 extents(scaledRadius, scaledRadius, scaledRadius); + + glm::vec3 start = glm::floor(center - extents); + glm::vec3 end = glm::ceil(center + extents); + + // raise/lower all points within the radius + float z = qMax(start.z, 0.0f); + float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); + uchar* lineDest = (uchar*)contents.data() + (int)z * size + (int)startX; + float squaredRadius = scaledRadius * scaledRadius; + float squaredRadiusReciprocal = 1.0f / squaredRadius; + const int EIGHT_BIT_MAXIMUM = 255; + float scaledHeight = _edit.height * EIGHT_BIT_MAXIMUM / info.size; + for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { + uchar* dest = lineDest; + for (float x = startX; x <= endX; x += 1.0f, dest++) { + float dx = x - center.x, dz = z - center.z; + float distanceSquared = dx * dx + dz * dz; + if (distanceSquared <= squaredRadius) { + // height falls off towards edges + int value = *dest + scaledHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; + *dest = qMin(qMax(value, 0), EIGHT_BIT_MAXIMUM); + } + } + lineDest += size; + } + + HeightfieldDataPointer newPointer(new HeightfieldData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + return STOP_RECURSION; +} + +void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + PaintHeightfieldHeightEditVisitor visitor(*this); + data.guide(visitor); +} + +PaintHeightfieldColorEdit::PaintHeightfieldColorEdit(const glm::vec3& position, float radius, const QColor& color) : + position(position), + radius(radius), + color(color) { +} + +class PaintHeightfieldColorEditVisitor : public MetavoxelVisitor { +public: + + PaintHeightfieldColorEditVisitor(const PaintHeightfieldColorEdit& edit); + + virtual int visit(MetavoxelInfo& info); + +private: + + PaintHeightfieldColorEdit _edit; + Box _bounds; +}; + +PaintHeightfieldColorEditVisitor::PaintHeightfieldColorEditVisitor(const PaintHeightfieldColorEdit& edit) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), + QVector() << AttributeRegistry::getInstance()->getHeightfieldColorAttribute()), + _edit(edit) { + + glm::vec3 extents(_edit.radius, _edit.radius, _edit.radius); + _bounds = Box(_edit.position - extents, _edit.position + extents); +} + +int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); + if (!pointer) { + return STOP_RECURSION; + } + QByteArray contents(pointer->getContents()); + const int BYTES_PER_PIXEL = 3; + int size = glm::sqrt((float)contents.size() / BYTES_PER_PIXEL); + int highest = size - 1; + float heightScale = highest / info.size; + + glm::vec3 center = (_edit.position - info.minimum) * heightScale; + float scaledRadius = _edit.radius * heightScale; + glm::vec3 extents(scaledRadius, scaledRadius, scaledRadius); + + glm::vec3 start = glm::floor(center - extents); + glm::vec3 end = glm::ceil(center + extents); + + // paint all points within the radius + float z = qMax(start.z, 0.0f); + float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); + int stride = size * BYTES_PER_PIXEL; + char* lineDest = contents.data() + (int)z * stride + (int)startX * BYTES_PER_PIXEL; + float squaredRadius = scaledRadius * scaledRadius; + char red = _edit.color.red(), green = _edit.color.green(), blue = _edit.color.blue(); + for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { + char* dest = lineDest; + for (float x = startX; x <= endX; x += 1.0f, dest += BYTES_PER_PIXEL) { + float dx = x - center.x, dz = z - center.z; + if (dx * dx + dz * dz <= squaredRadius) { + dest[0] = red; + dest[1] = green; + dest[2] = blue; + } + } + lineDest += stride; + } + + HeightfieldDataPointer newPointer(new HeightfieldData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + return STOP_RECURSION; +} + +void PaintHeightfieldColorEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + PaintHeightfieldColorEditVisitor visitor(*this); + data.guide(visitor); +} + diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 91d73c08a9..2fc8cbf030 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -207,4 +207,38 @@ public: DECLARE_STREAMABLE_METATYPE(SetDataEdit) +/// An edit that sets a region of a heightfield height. +class PaintHeightfieldHeightEdit : public MetavoxelEdit { + STREAMABLE + +public: + + STREAM glm::vec3 position; + STREAM float radius; + STREAM float height; + + PaintHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, float height = 0.0f); + + virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; +}; + +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) + #endif // hifi_MetavoxelMessages_h diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index a2b0586708..339c07a21e 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -138,6 +138,8 @@ public: QColorEditor(QWidget* parent); + const QColor& getColor() const { return _color; } + signals: void colorChanged(const QColor& color); diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index f17715ddfe..1b48a2e333 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -80,6 +80,8 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeAudioStreamStats: return 1; + case PacketTypeMetavoxelData: + return 1; default: return 0; }