diff --git a/interface/resources/shaders/metavoxel_heightfield.vert b/interface/resources/shaders/metavoxel_heightfield.vert index cc4f68e9e0..4932ad1b2b 100644 --- a/interface/resources/shaders/metavoxel_heightfield.vert +++ b/interface/resources/shaders/metavoxel_heightfield.vert @@ -17,6 +17,9 @@ uniform sampler2D heightMap; // the distance between height points in texture space uniform float heightScale; +// the scale between height and color textures +uniform float colorScale; + // the interpolated normal varying vec4 normal; @@ -29,8 +32,8 @@ void main(void) { texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r; normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0)); - // pass along the texture coordinates - gl_TexCoord[0] = gl_MultiTexCoord0; + // pass along the scaled/offset texture coordinates + gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * colorScale; // add the height to the position float height = texture2D(heightMap, heightCoord).r; diff --git a/interface/resources/shaders/metavoxel_heightfield_cursor.vert b/interface/resources/shaders/metavoxel_heightfield_cursor.vert index 20502fbdce..93ed36449e 100644 --- a/interface/resources/shaders/metavoxel_heightfield_cursor.vert +++ b/interface/resources/shaders/metavoxel_heightfield_cursor.vert @@ -14,9 +14,6 @@ // 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; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 597542778a..40a2ff0097 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -37,6 +37,9 @@ void MetavoxelSystem::init() { _pointBufferAttribute = AttributeRegistry::getInstance()->registerAttribute(new BufferDataAttribute("pointBuffer")); _heightfieldBufferAttribute = AttributeRegistry::getInstance()->registerAttribute( new BufferDataAttribute("heightfieldBuffer")); + + _heightfieldBufferAttribute->setLODThresholdMultiplier( + AttributeRegistry::getInstance()->getHeightfieldAttribute()->getLODThresholdMultiplier()); } MetavoxelLOD MetavoxelSystem::getLOD() { @@ -118,10 +121,271 @@ void MetavoxelSystem::render() { guideToAugmented(renderVisitor); } +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() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), 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; + } + const HeightfieldBuffer* buffer = static_cast( + info.inputValues.at(0).getInlineValue().data()); + if (!buffer) { + return STOP_RECURSION; + } + const QByteArray& contents = buffer->getHeight(); + const uchar* src = (const uchar*)contents.constData(); + int size = glm::sqrt((float)contents.size()); + int unextendedSize = size - HeightfieldBuffer::HEIGHT_EXTENSION; + int highest = HeightfieldBuffer::HEIGHT_BORDER + unextendedSize; + float heightScale = unextendedSize * EIGHT_BIT_MAXIMUM_RECIPROCAL; + + // find the initial location in heightfield coordinates + glm::vec3 entry = (_origin + distance * _direction - info.minimum) * (float)unextendedSize / info.size; + entry.x += HeightfieldBuffer::HEIGHT_BORDER; + entry.z += HeightfieldBuffer::HEIGHT_BORDER; + 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, HeightfieldBuffer::HEIGHT_BORDER), highest); + int floorZ = qMin(qMax((int)floors.z, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilX = qMin(qMax((int)ceils.x, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilZ = qMin(qMax((int)ceils.z, HeightfieldBuffer::HEIGHT_BORDER), 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 >= HeightfieldBuffer::HEIGHT_BORDER && 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) >= HeightfieldBuffer::HEIGHT_BORDER; + 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) >= HeightfieldBuffer::HEIGHT_BORDER; + 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 / unextendedSize)); + 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 / unextendedSize)); + return SHORT_CIRCUIT; + } + } + + // no joy; continue on our way + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + } + + return STOP_RECURSION; +} + +bool MetavoxelSystem::findFirstRayHeightfieldIntersection(const glm::vec3& origin, + const glm::vec3& direction, float& distance) { + RayHeightfieldIntersectionVisitor visitor(origin, direction, getLOD()); + guideToAugmented(visitor); + if (visitor.intersectionDistance == FLT_MAX) { + return false; + } + distance = visitor.intersectionDistance; + return true; +} + +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() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), 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; + } + const HeightfieldBuffer* buffer = static_cast( + info.inputValues.at(0).getInlineValue().data()); + if (!buffer) { + return STOP_RECURSION; + } + const QByteArray& contents = buffer->getHeight(); + const uchar* src = (const uchar*)contents.constData(); + int size = glm::sqrt((float)contents.size()); + int unextendedSize = size - HeightfieldBuffer::HEIGHT_EXTENSION; + int highest = HeightfieldBuffer::HEIGHT_BORDER + unextendedSize; + relative *= unextendedSize / info.size; + relative.x += HeightfieldBuffer::HEIGHT_BORDER; + relative.z += HeightfieldBuffer::HEIGHT_BORDER; + + // 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, HeightfieldBuffer::HEIGHT_BORDER), highest); + int floorZ = qMin(qMax((int)floors.z, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilX = qMin(qMax((int)ceils.x, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilZ = qMin(qMax((int)ceils.z, HeightfieldBuffer::HEIGHT_BORDER), highest); + float upperLeft = src[floorZ * size + floorX]; + float lowerRight = src[ceilZ * size + ceilX]; + float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z); + + // 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(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z), + (fracts.x - fracts.z) / (1.0f - fracts.z)); + + } else { + float lowerLeft = src[ceilZ * size + floorX]; + interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, 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 MetavoxelSystem::getHeightfieldHeight(const glm::vec3& location) { + HeightfieldHeightVisitor visitor(getLOD(), location); + guideToAugmented(visitor); + return visitor.height; +} + class HeightfieldCursorRenderVisitor : public MetavoxelVisitor { public: - HeightfieldCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds); + HeightfieldCursorRenderVisitor(const Box& bounds); virtual int visit(MetavoxelInfo& info); @@ -130,9 +394,9 @@ private: Box _bounds; }; -HeightfieldCursorRenderVisitor::HeightfieldCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds) : +HeightfieldCursorRenderVisitor::HeightfieldCursorRenderVisitor(const Box& bounds) : MetavoxelVisitor(QVector() << - Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector(), lod), + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute()), _bounds(bounds) { } @@ -172,7 +436,7 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glActiveTexture(GL_TEXTURE0); glm::vec3 extents(radius, radius, radius); - HeightfieldCursorRenderVisitor visitor(getLOD(), Box(position - extents, position + extents)); + HeightfieldCursorRenderVisitor visitor(Box(position - extents, position + extents)); guideToAugmented(visitor); DefaultMetavoxelRendererImplementation::getHeightfieldCursorProgram().release(); @@ -332,16 +596,32 @@ void PointBuffer::render(bool cursor) { _buffer.release(); } +const int HeightfieldBuffer::HEIGHT_BORDER = 1; +const int HeightfieldBuffer::SHARED_EDGE = 1; +const int HeightfieldBuffer::HEIGHT_EXTENSION = 2 * HeightfieldBuffer::HEIGHT_BORDER + HeightfieldBuffer::SHARED_EDGE; + HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, - const QByteArray& height, const QByteArray& color, bool clearAfterLoading) : + const QByteArray& height, const QByteArray& color) : _translation(translation), _scale(scale), + _heightBounds(translation, translation + glm::vec3(scale, scale, scale)), + _colorBounds(_heightBounds), _height(height), _color(color), - _clearAfterLoading(clearAfterLoading), _heightTextureID(0), _colorTextureID(0), - _heightSize(glm::sqrt(height.size())) { + _heightSize(glm::sqrt(height.size())), + _heightIncrement(scale / (_heightSize - HEIGHT_EXTENSION)), + _colorSize(glm::sqrt(color.size() / HeightfieldData::COLOR_BYTES)), + _colorIncrement(scale / (_colorSize - SHARED_EDGE)) { + + _heightBounds.minimum.x -= _heightIncrement * HEIGHT_BORDER; + _heightBounds.minimum.z -= _heightIncrement * HEIGHT_BORDER; + _heightBounds.maximum.x += _heightIncrement * (SHARED_EDGE + HEIGHT_BORDER); + _heightBounds.maximum.z += _heightIncrement * (SHARED_EDGE + HEIGHT_BORDER); + + _colorBounds.maximum.x += _colorIncrement * SHARED_EDGE; + _colorBounds.maximum.z += _colorIncrement * SHARED_EDGE; } HeightfieldBuffer::~HeightfieldBuffer() { @@ -355,6 +635,32 @@ HeightfieldBuffer::~HeightfieldBuffer() { } } +QByteArray HeightfieldBuffer::getUnextendedHeight() const { + int srcSize = glm::sqrt(_height.size()); + int destSize = srcSize - 3; + QByteArray unextended(destSize * destSize, 0); + const char* src = _height.constData() + srcSize + 1; + char* dest = unextended.data(); + for (int z = 0; z < destSize; z++, src += srcSize, dest += destSize) { + memcpy(dest, src, destSize); + } + return unextended; +} + +QByteArray HeightfieldBuffer::getUnextendedColor() const { + int srcSize = glm::sqrt(_color.size() / HeightfieldData::COLOR_BYTES); + int destSize = srcSize - 1; + QByteArray unextended(destSize * destSize * HeightfieldData::COLOR_BYTES, 0); + const char* src = _color.constData(); + int srcStride = srcSize * HeightfieldData::COLOR_BYTES; + char* dest = unextended.data(); + int destStride = destSize * HeightfieldData::COLOR_BYTES; + for (int z = 0; z < destSize; z++, src += srcStride, dest += destStride) { + memcpy(dest, src, destStride); + } + return unextended; +} + class HeightfieldPoint { public: glm::vec2 textureCoord; @@ -366,14 +672,12 @@ void HeightfieldBuffer::render(bool cursor) { if (_heightTextureID == 0) { glGenTextures(1, &_heightTextureID); glBindTexture(GL_TEXTURE_2D, _heightTextureID); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, _heightSize, _heightSize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, _height.constData()); - if (_clearAfterLoading) { - _height.clear(); - } glGenTextures(1, &_colorTextureID); glBindTexture(GL_TEXTURE_2D, _colorTextureID); @@ -385,32 +689,32 @@ void HeightfieldBuffer::render(bool cursor) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, WHITE_COLOR); } else { - int colorSize = glm::sqrt(_color.size() / 3); + int colorSize = glm::sqrt(_color.size() / HeightfieldData::COLOR_BYTES); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, colorSize, colorSize, 0, GL_RGB, GL_UNSIGNED_BYTE, _color.constData()); - if (_clearAfterLoading) { - _color.clear(); - } } } // create the buffer objects lazily - int sizeWithSkirt = _heightSize + 2; - int vertexCount = sizeWithSkirt * sizeWithSkirt; - int rows = sizeWithSkirt - 1; - int indexCount = rows * rows * 4; + int innerSize = _heightSize - 2 * HeightfieldBuffer::HEIGHT_BORDER; + int vertexCount = _heightSize * _heightSize; + int rows = _heightSize - 1; + int indexCount = rows * rows * 3 * 2; BufferPair& bufferPair = _bufferPairs[_heightSize]; if (!bufferPair.first.isCreated()) { QVector vertices(vertexCount); HeightfieldPoint* point = vertices.data(); - float step = 1.0f / (_heightSize - 1); - float z = -step; - for (int i = 0; i < sizeWithSkirt; i++, z += step) { - float x = -step; + float vertexStep = 1.0f / (innerSize - 1); + float z = -vertexStep; + float textureStep = 1.0f / _heightSize; + float t = textureStep / 2.0f; + for (int i = 0; i < _heightSize; i++, z += vertexStep, t += textureStep) { + float x = -vertexStep; + float s = textureStep / 2.0f; const float SKIRT_LENGTH = 0.25f; - float baseY = (i == 0 || i == sizeWithSkirt - 1) ? -SKIRT_LENGTH : 0.0f; - for (int j = 0; j < sizeWithSkirt; j++, point++, x += step) { - point->vertex = glm::vec3(x, (j == 0 || j == sizeWithSkirt - 1) ? -SKIRT_LENGTH : baseY, z); - point->textureCoord = glm::vec2(x, z); + float baseY = (i == 0 || i == _heightSize - 1) ? -SKIRT_LENGTH : 0.0f; + for (int j = 0; j < _heightSize; j++, point++, x += vertexStep, s += textureStep) { + point->vertex = glm::vec3(x, (j == 0 || j == _heightSize - 1) ? -SKIRT_LENGTH : baseY, z); + point->textureCoord = glm::vec2(s, t); } } @@ -422,13 +726,16 @@ void HeightfieldBuffer::render(bool cursor) { QVector indices(indexCount); int* index = indices.data(); for (int i = 0; i < rows; i++) { - int lineIndex = i * sizeWithSkirt; - int nextLineIndex = (i + 1) * sizeWithSkirt; + int lineIndex = i * _heightSize; + int nextLineIndex = (i + 1) * _heightSize; for (int j = 0; j < rows; j++) { *index++ = lineIndex + j; *index++ = nextLineIndex + j; + *index++ = nextLineIndex + j + 1; + *index++ = nextLineIndex + j + 1; *index++ = lineIndex + j + 1; + *index++ = lineIndex + j; } } @@ -452,18 +759,16 @@ void HeightfieldBuffer::render(bool cursor) { glBindTexture(GL_TEXTURE_2D, _heightTextureID); - int heightScaleLocation; - if (cursor) { - heightScaleLocation = DefaultMetavoxelRendererImplementation::getCursorHeightScaleLocation(); - } else { - heightScaleLocation = DefaultMetavoxelRendererImplementation::getHeightScaleLocation(); + if (!cursor) { + DefaultMetavoxelRendererImplementation::getHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getHeightScaleLocation(), 1.0f / _heightSize); + DefaultMetavoxelRendererImplementation::getHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getColorScaleLocation(), (float)_heightSize / innerSize); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, _colorTextureID); } - DefaultMetavoxelRendererImplementation::getHeightfieldProgram().setUniformValue(heightScaleLocation, 1.0f / _heightSize); - - glDrawRangeElements(GL_QUADS, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); if (!cursor) { glBindTexture(GL_TEXTURE_2D, 0); @@ -518,17 +823,19 @@ BufferDataAttribute::BufferDataAttribute(const QString& name) : } bool BufferDataAttribute::merge(void*& parent, void* children[], bool postRead) const { - BufferDataPointer firstChild = decodeInline(children[0]); - for (int i = 1; i < MERGE_COUNT; i++) { - if (firstChild != decodeInline(children[i])) { - *(BufferDataPointer*)&parent = _defaultValue; + *(BufferDataPointer*)&parent = _defaultValue; + for (int i = 0; i < MERGE_COUNT; i++) { + if (decodeInline(children[i])) { return false; } } - *(BufferDataPointer*)&parent = firstChild; return true; } +AttributeValue BufferDataAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + void DefaultMetavoxelRendererImplementation::init() { if (!_pointProgram.isLinked()) { _pointProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_point.vert"); @@ -548,6 +855,7 @@ void DefaultMetavoxelRendererImplementation::init() { _heightfieldProgram.setUniformValue("heightMap", 0); _heightfieldProgram.setUniformValue("diffuseMap", 1); _heightScaleLocation = _heightfieldProgram.uniformLocation("heightScale"); + _colorScaleLocation = _heightfieldProgram.uniformLocation("colorScale"); _heightfieldProgram.release(); _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + @@ -558,7 +866,6 @@ void DefaultMetavoxelRendererImplementation::init() { _heightfieldCursorProgram.bind(); _heightfieldCursorProgram.setUniformValue("heightMap", 0); - _cursorHeightScaleLocation = _heightfieldCursorProgram.uniformLocation("heightScale"); _heightfieldCursorProgram.release(); } } @@ -634,33 +941,279 @@ bool PointAugmentVisitor::postVisit(MetavoxelInfo& info) { return true; } -class HeightfieldAugmentVisitor : public MetavoxelVisitor { +class HeightfieldFetchVisitor : public MetavoxelVisitor { public: - - HeightfieldAugmentVisitor(const MetavoxelLOD& lod); + + HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections); + + void init(HeightfieldBuffer* buffer) { _buffer = buffer; } virtual int visit(MetavoxelInfo& info); + +private: + + const QVector& _intersections; + HeightfieldBuffer* _buffer; }; -HeightfieldAugmentVisitor::HeightfieldAugmentVisitor(const MetavoxelLOD& lod) : +HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << - AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector() << - Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod) { + AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector(), lod), + _intersections(intersections) { } -int HeightfieldAugmentVisitor::visit(MetavoxelInfo& info) { - if (info.isLeaf) { - HeightfieldBuffer* buffer = NULL; - HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); - if (height) { - HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); - buffer = new HeightfieldBuffer(info.minimum, info.size, height->getContents(), - color ? color->getContents() : QByteArray()); - } - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer))); +int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { + Box bounds = info.getBounds(); + const Box& heightBounds = _buffer->getHeightBounds(); + if (!bounds.intersects(heightBounds)) { return STOP_RECURSION; } - return DEFAULT_ORDER; + if (!info.isLeaf && info.size > _buffer->getScale()) { + return DEFAULT_ORDER; + } + HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + if (!height) { + return STOP_RECURSION; + } + foreach (const Box& intersection, _intersections) { + Box overlap = intersection.getIntersection(bounds); + if (overlap.isEmpty()) { + continue; + } + float heightIncrement = _buffer->getHeightIncrement(); + int destX = (overlap.minimum.x - heightBounds.minimum.x) / heightIncrement; + int destY = (overlap.minimum.z - heightBounds.minimum.z) / heightIncrement; + int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / heightIncrement); + int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / heightIncrement); + int heightSize = _buffer->getHeightSize(); + char* dest = _buffer->getHeight().data() + destY * heightSize + destX; + + const QByteArray& srcHeight = height->getContents(); + int srcSize = glm::sqrt(srcHeight.size()); + float srcIncrement = info.size / srcSize; + + if (info.size == _buffer->getScale() && srcSize == (heightSize - HeightfieldBuffer::HEIGHT_EXTENSION)) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const char* src = srcHeight.constData() + srcY * srcSize + srcX; + for (int y = 0; y < destHeight; y++, src += srcSize, dest += heightSize) { + memcpy(dest, src, destWidth); + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = heightIncrement / srcIncrement; + int shift = 0; + float size = _buffer->getScale(); + while (size < info.size) { + shift++; + size *= 2.0f; + } + const int EIGHT_BIT_MAXIMUM = 255; + int subtract = (_buffer->getTranslation().y - info.minimum.y) * EIGHT_BIT_MAXIMUM / _buffer->getScale(); + for (int y = 0; y < destHeight; y++, dest += heightSize, srcY += srcAdvance) { + const uchar* src = (const uchar*)srcHeight.constData() + (int)srcY * srcSize; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) { + *lineDest = qMin(qMax(0, (src[(int)lineSrcX] << shift) - subtract), EIGHT_BIT_MAXIMUM); + } + } + } + + int colorSize = _buffer->getColorSize(); + if (colorSize == 0) { + return STOP_RECURSION; + } + HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + if (!color) { + return STOP_RECURSION; + } + const Box& colorBounds = _buffer->getColorBounds(); + overlap = colorBounds.getIntersection(overlap); + float colorIncrement = _buffer->getColorIncrement(); + destX = (overlap.minimum.x - colorBounds.minimum.x) / colorIncrement; + destY = (overlap.minimum.z - colorBounds.minimum.z) / colorIncrement; + destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / colorIncrement); + destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / colorIncrement); + dest = _buffer->getColor().data() + (destY * colorSize + destX) * HeightfieldData::COLOR_BYTES; + int destStride = colorSize * HeightfieldData::COLOR_BYTES; + int destBytes = destWidth * HeightfieldData::COLOR_BYTES; + + const QByteArray& srcColor = color->getContents(); + srcSize = glm::sqrt(srcColor.size() / HeightfieldData::COLOR_BYTES); + int srcStride = srcSize * HeightfieldData::COLOR_BYTES; + srcIncrement = info.size / srcSize; + + if (srcIncrement == colorIncrement) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const char* src = srcColor.constData() + (srcY * srcSize + srcX) * HeightfieldData::COLOR_BYTES; + for (int y = 0; y < destHeight; y++, src += srcStride, dest += destStride) { + memcpy(dest, src, destBytes); + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = colorIncrement / srcIncrement; + for (int y = 0; y < destHeight; y++, dest += destStride, srcY += srcAdvance) { + const char* src = srcColor.constData() + (int)srcY * srcStride; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destBytes; lineDest != end; lineDest += HeightfieldData::COLOR_BYTES, + lineSrcX += srcAdvance) { + const char* lineSrc = src + (int)lineSrcX * HeightfieldData::COLOR_BYTES; + lineDest[0] = lineSrc[0]; + lineDest[1] = lineSrc[1]; + lineDest[2] = lineSrc[2]; + } + } + } + } + return STOP_RECURSION; +} + +class HeightfieldRegionVisitor : public MetavoxelVisitor { +public: + + QVector regions; + Box regionBounds; + + HeightfieldRegionVisitor(const MetavoxelLOD& lod); + + virtual int visit(MetavoxelInfo& info); + +private: + + void addRegion(const Box& unextended, const Box& extended); + + QVector _intersections; + HeightfieldFetchVisitor _fetchVisitor; +}; + +HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << + AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod), + regionBounds(glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX), glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)), + _fetchVisitor(lod, _intersections) { +} + +int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + HeightfieldBuffer* buffer = NULL; + HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + if (height) { + const QByteArray& heightContents = height->getContents(); + int size = glm::sqrt(heightContents.size()); + int extendedSize = size + HeightfieldBuffer::HEIGHT_EXTENSION; + int heightContentsSize = extendedSize * extendedSize; + + HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + int colorContentsSize = 0; + if (color) { + const QByteArray& colorContents = color->getContents(); + int colorSize = glm::sqrt(colorContents.size() / HeightfieldData::COLOR_BYTES); + int extendedColorSize = colorSize + HeightfieldBuffer::SHARED_EDGE; + colorContentsSize = extendedColorSize * extendedColorSize * HeightfieldData::COLOR_BYTES; + } + + const HeightfieldBuffer* existingBuffer = static_cast( + info.inputValues.at(2).getInlineValue().data()); + Box bounds = info.getBounds(); + if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize && + existingBuffer->getColor().size() == colorContentsSize) { + // we already have a buffer of the correct resolution + addRegion(bounds, existingBuffer->getHeightBounds()); + return STOP_RECURSION; + } + // we must create a new buffer and update its borders + buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), + QByteArray(colorContentsSize, 0)); + const Box& heightBounds = buffer->getHeightBounds(); + addRegion(bounds, heightBounds); + + _intersections.clear(); + _intersections.append(Box(heightBounds.minimum, + glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); + _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), + glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); + _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), + heightBounds.maximum)); + _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), + glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); + + _fetchVisitor.init(buffer); + _data->guide(_fetchVisitor); + } + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer))); + return STOP_RECURSION; +} + +void HeightfieldRegionVisitor::addRegion(const Box& unextended, const Box& extended) { + regions.append(unextended); + regionBounds.add(extended); +} + +class HeightfieldUpdateVisitor : public MetavoxelVisitor { +public: + + HeightfieldUpdateVisitor(const MetavoxelLOD& lod, const QVector& regions, const Box& regionBounds); + + virtual int visit(MetavoxelInfo& info); + +private: + + const QVector& _regions; + const Box& _regionBounds; + QVector _intersections; + HeightfieldFetchVisitor _fetchVisitor; +}; + +HeightfieldUpdateVisitor::HeightfieldUpdateVisitor(const MetavoxelLOD& lod, const QVector& regions, + const Box& regionBounds) : + MetavoxelVisitor(QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod), + _regions(regions), + _regionBounds(regionBounds), + _fetchVisitor(lod, _intersections) { +} + +int HeightfieldUpdateVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_regionBounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + const HeightfieldBuffer* buffer = static_cast( + info.inputValues.at(0).getInlineValue().data()); + if (!buffer) { + return STOP_RECURSION; + } + _intersections.clear(); + foreach (const Box& region, _regions) { + if (region.intersects(buffer->getHeightBounds())) { + _intersections.append(region.getIntersection(buffer->getHeightBounds())); + } + } + if (_intersections.isEmpty()) { + return STOP_RECURSION; + } + HeightfieldBuffer* newBuffer = new HeightfieldBuffer(info.minimum, info.size, + buffer->getHeight(), buffer->getColor()); + _fetchVisitor.init(newBuffer); + _data->guide(_fetchVisitor); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(newBuffer))); + return STOP_RECURSION; } void DefaultMetavoxelRendererImplementation::augment(MetavoxelData& data, const MetavoxelData& previous, @@ -687,8 +1240,12 @@ void DefaultMetavoxelRendererImplementation::augment(MetavoxelData& data, const PointAugmentVisitor pointAugmentVisitor(lod); data.guideToDifferent(expandedPrevious, pointAugmentVisitor); - HeightfieldAugmentVisitor heightfieldAugmentVisitor(lod); - data.guideToDifferent(expandedPrevious, heightfieldAugmentVisitor); + HeightfieldRegionVisitor heightfieldRegionVisitor(lod); + data.guideToDifferent(expandedPrevious, heightfieldRegionVisitor); + + HeightfieldUpdateVisitor heightfieldUpdateVisitor(lod, heightfieldRegionVisitor.regions, + heightfieldRegionVisitor.regionBounds); + data.guide(heightfieldUpdateVisitor); } class SpannerSimulateVisitor : public SpannerVisitor { @@ -760,7 +1317,7 @@ bool SpannerRenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, class BufferRenderVisitor : public MetavoxelVisitor { public: - BufferRenderVisitor(const AttributePointer& attribute, const MetavoxelLOD& lod); + BufferRenderVisitor(const AttributePointer& attribute); virtual int visit(MetavoxelInfo& info); @@ -770,8 +1327,8 @@ private: int _containmentDepth; }; -BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute, const MetavoxelLOD& lod) : - MetavoxelVisitor(QVector() << attribute, QVector(), lod), +BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute) : + MetavoxelVisitor(QVector() << attribute), _order(encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())), _containmentDepth(INT_MAX) { } @@ -785,11 +1342,14 @@ int BufferRenderVisitor::visit(MetavoxelInfo& info) { } _containmentDepth = (intersection == Frustum::CONTAINS_INTERSECTION) ? _depth : INT_MAX; } + if (!info.isLeaf) { + return _order; + } BufferDataPointer buffer = info.inputValues.at(0).getInlineValue(); if (buffer) { buffer->render(); } - return info.isLeaf ? STOP_RECURSION : _order; + return STOP_RECURSION; } void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod) { @@ -818,7 +1378,7 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox glDisable(GL_BLEND); - BufferRenderVisitor pointRenderVisitor(Application::getInstance()->getMetavoxels()->getPointBufferAttribute(), lod); + BufferRenderVisitor pointRenderVisitor(Application::getInstance()->getMetavoxels()->getPointBufferAttribute()); data.guide(pointRenderVisitor); glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); @@ -838,8 +1398,7 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox glEnableClientState(GL_TEXTURE_COORD_ARRAY); - BufferRenderVisitor heightfieldRenderVisitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), - lod); + BufferRenderVisitor heightfieldRenderVisitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute()); data.guide(heightfieldRenderVisitor); _heightfieldProgram.release(); @@ -856,8 +1415,8 @@ ProgramObject DefaultMetavoxelRendererImplementation::_pointProgram; int DefaultMetavoxelRendererImplementation::_pointScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldProgram; int DefaultMetavoxelRendererImplementation::_heightScaleLocation; +int DefaultMetavoxelRendererImplementation::_colorScaleLocation; 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 7b681ff3a9..051c5da711 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -45,6 +45,10 @@ public: void renderHeightfieldCursor(const glm::vec3& position, float radius); + bool findFirstRayHeightfieldIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance); + + Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location); + Q_INVOKABLE void deleteTextures(int heightID, int colorID); protected: @@ -131,29 +135,50 @@ private: class HeightfieldBuffer : public BufferData { public: - /// Creates a new heightfield buffer. - /// \param clearAfterLoading if true, clear the data arrays after we load them into textures in order to reclaim the space - HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color, - bool clearAfterLoading = true); + static const int HEIGHT_BORDER; + static const int SHARED_EDGE; + static const int HEIGHT_EXTENSION; + + HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color); ~HeightfieldBuffer(); const glm::vec3& getTranslation() const { return _translation; } + float getScale() const { return _scale; } + const Box& getHeightBounds() const { return _heightBounds; } + const Box& getColorBounds() const { return _colorBounds; } + + QByteArray& getHeight() { return _height; } const QByteArray& getHeight() const { return _height; } + + QByteArray& getColor() { return _color; } const QByteArray& getColor() const { return _color; } + QByteArray getUnextendedHeight() const; + QByteArray getUnextendedColor() const; + + int getHeightSize() const { return _heightSize; } + float getHeightIncrement() const { return _heightIncrement; } + + int getColorSize() const { return _colorSize; } + float getColorIncrement() const { return _colorIncrement; } + virtual void render(bool cursor = false); private: glm::vec3 _translation; float _scale; + Box _heightBounds; + Box _colorBounds; QByteArray _height; QByteArray _color; - bool _clearAfterLoading; GLuint _heightTextureID; GLuint _colorTextureID; int _heightSize; + float _heightIncrement; + int _colorSize; + float _colorIncrement; typedef QPair BufferPair; static QHash _bufferPairs; @@ -182,6 +207,8 @@ public: Q_INVOKABLE BufferDataAttribute(const QString& name = QString()); virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; /// Renders metavoxels as points. @@ -194,9 +221,9 @@ public: static ProgramObject& getHeightfieldProgram() { return _heightfieldProgram; } static int getHeightScaleLocation() { return _heightScaleLocation; } - + static int getColorScaleLocation() { return _colorScaleLocation; } + static ProgramObject& getHeightfieldCursorProgram() { return _heightfieldCursorProgram; } - static int getCursorHeightScaleLocation() { return _cursorHeightScaleLocation; } Q_INVOKABLE DefaultMetavoxelRendererImplementation(); @@ -211,9 +238,9 @@ private: static ProgramObject _heightfieldProgram; static int _heightScaleLocation; + static int _colorScaleLocation; 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 d35ed93f1b..b7057532fb 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -956,11 +956,11 @@ void ImportHeightfieldTool::apply() { HeightfieldBuffer* buffer = static_cast(bufferData.data()); MetavoxelData data; data.setSize(scale); - HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getHeight())); + HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getUnextendedHeight())); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer)))); if (!buffer->getColor().isEmpty()) { - HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getColor())); + HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getUnextendedColor())); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); } @@ -1003,34 +1003,38 @@ void ImportHeightfieldTool::updatePreview() { if (_heightImage.width() > 0 && _heightImage.height() > 0) { float z = 0.0f; int blockSize = pow(2.0, _blockSize->value()); - int blockAdvancement = blockSize - 1; - for (int i = 0; i < _heightImage.height(); i += blockAdvancement, z++) { + int heightSize = blockSize + HeightfieldBuffer::HEIGHT_EXTENSION; + int colorSize = blockSize + HeightfieldBuffer::SHARED_EDGE; + for (int i = 0; i < _heightImage.height(); i += blockSize, z++) { float x = 0.0f; - for (int j = 0; j < _heightImage.width(); j += blockAdvancement, x++) { - QByteArray height(blockSize * blockSize, 0); - int rows = qMin(blockSize, _heightImage.height() - i); - int columns = qMin(blockSize, _heightImage.width() - j); - const int BYTES_PER_COLOR = 3; + for (int j = 0; j < _heightImage.width(); j += blockSize, x++) { + QByteArray height(heightSize * heightSize, 0); + int extendedI = qMax(i - HeightfieldBuffer::HEIGHT_BORDER, 0); + int extendedJ = qMax(j - HeightfieldBuffer::HEIGHT_BORDER, 0); + int offsetY = extendedI - i + HeightfieldBuffer::HEIGHT_BORDER; + int offsetX = extendedJ - j + HeightfieldBuffer::HEIGHT_BORDER; + int rows = qMin(heightSize - offsetY, _heightImage.height() - extendedI); + int columns = qMin(heightSize - offsetX, _heightImage.width() - extendedJ); for (int y = 0; y < rows; y++) { - uchar* src = _heightImage.scanLine(i + y) + j * BYTES_PER_COLOR; - char* dest = height.data() + y * blockSize; + uchar* src = _heightImage.scanLine(extendedI + y) + extendedJ * HeightfieldData::COLOR_BYTES; + char* dest = height.data() + (y + offsetY) * heightSize + offsetX; for (int x = 0; x < columns; x++) { *dest++ = *src; - src += BYTES_PER_COLOR; + src += HeightfieldData::COLOR_BYTES; } } - QByteArray color; if (!_colorImage.isNull()) { - color = QByteArray(blockSize * blockSize * BYTES_PER_COLOR, 0); - rows = qMax(0, qMin(blockSize, _colorImage.height() - i)); - columns = qMax(0, qMin(blockSize, _colorImage.width() - j)); + color = QByteArray(colorSize * colorSize * HeightfieldData::COLOR_BYTES, 0); + rows = qMax(0, qMin(colorSize, _colorImage.height() - i)); + columns = qMax(0, qMin(colorSize, _colorImage.width() - j)); for (int y = 0; y < rows; y++) { - memcpy(color.data() + y * blockSize * BYTES_PER_COLOR, - _colorImage.scanLine(i + y) + j * BYTES_PER_COLOR, columns * BYTES_PER_COLOR); + memcpy(color.data() + y * colorSize * HeightfieldData::COLOR_BYTES, + _colorImage.scanLine(i + y) + j * HeightfieldData::COLOR_BYTES, + columns * HeightfieldData::COLOR_BYTES); } } - buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, height, color, false))); + buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, height, color))); } } } diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 9f656ef5d3..1e30aee576 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -489,22 +489,92 @@ HeightfieldData::HeightfieldData(const QByteArray& contents) : _contents(contents) { } -const int BYTES_PER_PIXEL = 3; +HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) { + read(in, bytes, color); +} -HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) : - _encoded(in.readAligned(bytes)) { - - QImage image = QImage::fromData(_encoded).convertToFormat(QImage::Format_RGB888); - if (color) { - _contents.resize(image.width() * image.height() * BYTES_PER_PIXEL); - memcpy(_contents.data(), image.constBits(), _contents.size()); +enum HeightfieldImage { NULL_HEIGHTFIELD_IMAGE, NORMAL_HEIGHTFIELD_IMAGE, DEFLATED_HEIGHTFIELD_IMAGE }; + +static QByteArray encodeHeightfieldImage(const QImage& image) { + if (image.isNull()) { + return QByteArray(1, NULL_HEIGHTFIELD_IMAGE); + } + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + const int JPEG_ENCODE_THRESHOLD = 16; + if (image.width() >= JPEG_ENCODE_THRESHOLD && image.height() >= JPEG_ENCODE_THRESHOLD) { + qint32 offsetX = image.offset().x(), offsetY = image.offset().y(); + buffer.write((char*)&offsetX, sizeof(qint32)); + buffer.write((char*)&offsetY, sizeof(qint32)); + image.save(&buffer, "JPG"); + return QByteArray(1, DEFLATED_HEIGHTFIELD_IMAGE) + qCompress(buffer.data()); } else { - _contents.resize(image.width() * image.height()); - char* dest = _contents.data(); - for (const uchar* src = image.constBits(), *end = src + _contents.size() * BYTES_PER_PIXEL; - src != end; src += BYTES_PER_PIXEL) { - *dest++ = *src; + buffer.putChar(NORMAL_HEIGHTFIELD_IMAGE); + image.save(&buffer, "PNG"); + return buffer.data(); + } +} + +const QImage decodeHeightfieldImage(const QByteArray& data) { + switch (data.at(0)) { + case NULL_HEIGHTFIELD_IMAGE: + default: + return QImage(); + + case NORMAL_HEIGHTFIELD_IMAGE: + return QImage::fromData(QByteArray::fromRawData(data.constData() + 1, data.size() - 1)); + + case DEFLATED_HEIGHTFIELD_IMAGE: { + QByteArray inflated = qUncompress((const uchar*)data.constData() + 1, data.size() - 1); + const int OFFSET_SIZE = sizeof(qint32) * 2; + QImage image = QImage::fromData((const uchar*)inflated.constData() + OFFSET_SIZE, inflated.size() - OFFSET_SIZE); + const qint32* offsets = (const qint32*)inflated.constData(); + image.setOffset(QPoint(offsets[0], offsets[1])); + return image; + } + } +} + +HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color) { + if (!reference) { + read(in, bytes, color); + return; + } + QMutexLocker locker(&reference->_encodedDeltaMutex); + reference->_encodedDelta = in.readAligned(bytes); + reference->_deltaData = this; + _contents = reference->_contents; + QImage image = decodeHeightfieldImage(reference->_encodedDelta); + if (image.isNull()) { + return; + } + QPoint offset = image.offset(); + image = image.convertToFormat(QImage::Format_RGB888); + if (offset.x() == 0) { + set(image, color); + return; + } + int minX = offset.x() - 1; + int minY = offset.y() - 1; + if (color) { + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + char* dest = _contents.data() + (minY * size + minX) * COLOR_BYTES; + int destStride = size * COLOR_BYTES; + int srcStride = image.width() * COLOR_BYTES; + for (int y = 0; y < image.height(); y++) { + memcpy(dest, image.constScanLine(y), srcStride); + dest += destStride; + } + } else { + int size = glm::sqrt((float)_contents.size()); + char* lineDest = _contents.data() + minY * size + minX; + for (int y = 0; y < image.height(); y++) { + const uchar* src = image.constScanLine(y); + for (char* dest = lineDest, *end = dest + image.width(); dest != end; dest++, src += COLOR_BYTES) { + *dest = *src; + } + lineDest += size; } } } @@ -514,7 +584,7 @@ void HeightfieldData::write(Bitstream& out, bool color) { if (_encoded.isEmpty()) { QImage image; if (color) { - int size = glm::sqrt(_contents.size() / (float)BYTES_PER_PIXEL); + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); image = QImage((uchar*)_contents.data(), size, size, QImage::Format_RGB888); } else { int size = glm::sqrt((float)_contents.size()); @@ -526,38 +596,170 @@ void HeightfieldData::write(Bitstream& out, bool color) { *dest++ = *src; } } - QBuffer buffer(&_encoded); - buffer.open(QIODevice::WriteOnly); - image.save(&buffer, "JPG"); + _encoded = encodeHeightfieldImage(image); } out << _encoded.size(); out.writeAligned(_encoded); } +void HeightfieldData::writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color) { + if (!reference || reference->getContents().size() != _contents.size()) { + write(out, color); + return; + } + QMutexLocker locker(&reference->_encodedDeltaMutex); + if (reference->_encodedDelta.isEmpty() || reference->_deltaData != this) { + QImage image; + int minX, minY; + if (color) { + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + minX = size; + minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->_contents.constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++, src += COLOR_BYTES, ref += COLOR_BYTES) { + if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + if (maxX >= minX) { + int width = maxX - minX + 1; + int height = maxY - minY + 1; + image = QImage(width, height, QImage::Format_RGB888); + src = _contents.constData() + (minY * size + minX) * COLOR_BYTES; + int srcStride = size * COLOR_BYTES; + int destStride = width * COLOR_BYTES; + for (int y = 0; y < height; y++) { + memcpy(image.scanLine(y), src, destStride); + src += srcStride; + } + } + } else { + int size = glm::sqrt((float)_contents.size()); + minX = size; + minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->_contents.constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++) { + if (*src++ != *ref++) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + if (maxX >= minX) { + int width = qMax(maxX - minX + 1, 0); + int height = qMax(maxY - minY + 1, 0); + image = QImage(width, height, QImage::Format_RGB888); + const uchar* lineSrc = (const uchar*)_contents.constData() + minY * size + minX; + for (int y = 0; y < height; y++) { + uchar* dest = image.scanLine(y); + for (const uchar* src = lineSrc, *end = src + width; src != end; src++) { + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + } + lineSrc += size; + } + } + } + image.setOffset(QPoint(minX + 1, minY + 1)); + reference->_encodedDelta = encodeHeightfieldImage(image); + reference->_deltaData = this; + } + out << reference->_encodedDelta.size(); + out.writeAligned(reference->_encodedDelta); +} + +void HeightfieldData::read(Bitstream& in, int bytes, bool color) { + set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888), color); +} + +void HeightfieldData::set(const QImage& image, bool color) { + if (color) { + _contents.resize(image.width() * image.height() * COLOR_BYTES); + memcpy(_contents.data(), image.constBits(), _contents.size()); + + } else { + _contents.resize(image.width() * image.height()); + char* dest = _contents.data(); + for (const uchar* src = image.constBits(), *end = src + _contents.size() * COLOR_BYTES; + src != end; src += COLOR_BYTES) { + *dest++ = *src; + } + } +} + HeightfieldAttribute::HeightfieldAttribute(const QString& name) : InlineAttribute(name) { } void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (isLeaf) { - int size; - in >> size; - if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); - } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false)); - } + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false)); } } void HeightfieldAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (isLeaf) { - HeightfieldDataPointer data = decodeInline(value); - if (data) { - data->write(out, false); - } else { - out << 0; - } + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->write(out, false); + } else { + out << 0; + } +} + +void HeightfieldAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( + in, size, decodeInline(reference), false)); + } +} + +void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->writeDelta(out, decodeInline(reference), false); + } else { + out << 0; } } @@ -635,25 +837,53 @@ HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) : } void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (isLeaf) { - int size; - in >> size; - if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); - } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true)); - } + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true)); } } void HeightfieldColorAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (isLeaf) { - HeightfieldDataPointer data = decodeInline(value); - if (data) { - data->write(out, true); - } else { - out << 0; - } + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->write(out, true); + } else { + out << 0; + } +} + +void HeightfieldColorAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( + in, size, decodeInline(reference), true)); + } +} + +void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->writeDelta(out, decodeInline(reference), true); + } else { + out << 0; } } @@ -669,8 +899,8 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); return true; } - int size = glm::sqrt(maxSize / (float)BYTES_PER_PIXEL); - QByteArray contents(size * size * BYTES_PER_PIXEL, 0); + int size = glm::sqrt(maxSize / (float)HeightfieldData::COLOR_BYTES); + QByteArray contents(size * size * HeightfieldData::COLOR_BYTES, 0); int halfSize = size / 2; for (int i = 0; i < MERGE_COUNT; i++) { HeightfieldDataPointer child = decodeInline(children[i]); @@ -678,7 +908,7 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post continue; } const QByteArray& childContents = child->getContents(); - int childSize = glm::sqrt(childContents.size() / (float)BYTES_PER_PIXEL); + int childSize = glm::sqrt(childContents.size() / (float)HeightfieldData::COLOR_BYTES); const int INDEX_MASK = 1; int xIndex = i & INDEX_MASK; const int Y_SHIFT = 1; @@ -688,24 +918,25 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post } int Z_SHIFT = 2; int zIndex = (i >> Z_SHIFT) & INDEX_MASK; - char* dest = contents.data() + ((zIndex * halfSize * size) + (xIndex * halfSize)) * BYTES_PER_PIXEL; + char* dest = contents.data() + ((zIndex * halfSize * size) + (xIndex * halfSize)) * HeightfieldData::COLOR_BYTES; uchar* src = (uchar*)childContents.data(); - int childStride = childSize * BYTES_PER_PIXEL; - int stride = size * BYTES_PER_PIXEL; + int childStride = childSize * HeightfieldData::COLOR_BYTES; + int stride = size * HeightfieldData::COLOR_BYTES; int halfStride = stride / 2; - int childStep = 2 * BYTES_PER_PIXEL; - int redOffset3 = childStride + BYTES_PER_PIXEL; - int greenOffset1 = BYTES_PER_PIXEL + 1; + int childStep = 2 * HeightfieldData::COLOR_BYTES; + int redOffset3 = childStride + HeightfieldData::COLOR_BYTES; + int greenOffset1 = HeightfieldData::COLOR_BYTES + 1; int greenOffset2 = childStride + 1; - int greenOffset3 = childStride + BYTES_PER_PIXEL + 1; - int blueOffset1 = BYTES_PER_PIXEL + 2; + int greenOffset3 = childStride + HeightfieldData::COLOR_BYTES + 1; + int blueOffset1 = HeightfieldData::COLOR_BYTES + 2; int blueOffset2 = childStride + 2; - int blueOffset3 = childStride + BYTES_PER_PIXEL + 2; + int blueOffset3 = childStride + HeightfieldData::COLOR_BYTES + 2; if (childSize == size) { // simple case: one destination value for four child values for (int z = 0; z < halfSize; z++) { - for (char* end = dest + halfSize * BYTES_PER_PIXEL; dest != end; src += childStep) { - *dest++ = ((int)src[0] + (int)src[BYTES_PER_PIXEL] + (int)src[childStride] + (int)src[redOffset3]) >> 2; + for (char* end = dest + halfSize * HeightfieldData::COLOR_BYTES; dest != end; src += childStep) { + *dest++ = ((int)src[0] + (int)src[HeightfieldData::COLOR_BYTES] + + (int)src[childStride] + (int)src[redOffset3]) >> 2; *dest++ = ((int)src[1] + (int)src[greenOffset1] + (int)src[greenOffset2] + (int)src[greenOffset3]) >> 2; *dest++ = ((int)src[2] + (int)src[blueOffset1] + (int)src[blueOffset2] + (int)src[blueOffset3]) >> 2; } @@ -717,13 +948,14 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post int halfChildSize = childSize / 2; int destPerSrc = size / childSize; for (int z = 0; z < halfChildSize; z++) { - for (uchar* end = src + childSize * BYTES_PER_PIXEL; src != end; src += childStep) { - *dest++ = ((int)src[0] + (int)src[BYTES_PER_PIXEL] + (int)src[childStride] + (int)src[redOffset3]) >> 2; + for (uchar* end = src + childSize * HeightfieldData::COLOR_BYTES; src != end; src += childStep) { + *dest++ = ((int)src[0] + (int)src[HeightfieldData::COLOR_BYTES] + + (int)src[childStride] + (int)src[redOffset3]) >> 2; *dest++ = ((int)src[1] + (int)src[greenOffset1] + (int)src[greenOffset2] + (int)src[greenOffset3]) >> 2; *dest++ = ((int)src[2] + (int)src[blueOffset1] + (int)src[blueOffset2] + (int)src[blueOffset3]) >> 2; for (int j = 1; j < destPerSrc; j++) { - memcpy(dest, dest - BYTES_PER_PIXEL, BYTES_PER_PIXEL); - dest += BYTES_PER_PIXEL; + memcpy(dest, dest - HeightfieldData::COLOR_BYTES, HeightfieldData::COLOR_BYTES); + dest += HeightfieldData::COLOR_BYTES; } } dest += halfStride; diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 5d973341ad..ddf6105662 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -28,6 +28,7 @@ class QScriptEngine; class QScriptValue; class Attribute; +class HeightfieldData; class MetavoxelData; class MetavoxelLOD; class MetavoxelNode; @@ -421,26 +422,37 @@ public: virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; +typedef QExplicitlySharedDataPointer HeightfieldDataPointer; + /// Contains a block of heightfield data. class HeightfieldData : public QSharedData { public: + static const int COLOR_BYTES = 3; + HeightfieldData(const QByteArray& contents); HeightfieldData(Bitstream& in, int bytes, bool color); - + HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color); + const QByteArray& getContents() const { return _contents; } void write(Bitstream& out, bool color); + void writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color); private: + void read(Bitstream& in, int bytes, bool color); + void set(const QImage& image, bool color); + QByteArray _contents; QByteArray _encoded; QMutex _encodedMutex; + + HeightfieldDataPointer _deltaData; + QByteArray _encodedDelta; + QMutex _encodedDeltaMutex; }; -typedef QExplicitlySharedDataPointer HeightfieldDataPointer; - /// An attribute that stores heightfield data. class HeightfieldAttribute : public InlineAttribute { Q_OBJECT @@ -451,7 +463,10 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; - + + virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const; + virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; }; @@ -466,6 +481,9 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; + virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const; + virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; }; diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index 70b574d2a2..a2d3410314 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -61,180 +61,6 @@ 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); @@ -252,84 +78,6 @@ 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 8fc9bcd38d..333b709b9e 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -37,16 +37,12 @@ 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 926d839991..df6e8172e4 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -361,7 +361,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { QByteArray contents(pointer->getContents()); int size = glm::sqrt((float)contents.size()); int highest = size - 1; - float heightScale = highest / info.size; + float heightScale = size / info.size; glm::vec3 center = (_edit.position - info.minimum) * heightScale; float scaledRadius = _edit.radius * heightScale; @@ -378,6 +378,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { float squaredRadiusReciprocal = 1.0f / squaredRadius; const int EIGHT_BIT_MAXIMUM = 255; float scaledHeight = _edit.height * EIGHT_BIT_MAXIMUM / info.size; + bool changed = false; 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++) { @@ -386,14 +387,18 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { if (distanceSquared <= squaredRadius) { // height falls off towards edges int value = *dest + scaledHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; - *dest = qMin(qMax(value, 0), EIGHT_BIT_MAXIMUM); + if (value != *dest) { + *dest = qMin(qMax(value, 0), EIGHT_BIT_MAXIMUM); + changed = true; + } } } lineDest += size; } - - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + if (changed) { + HeightfieldDataPointer newPointer(new HeightfieldData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + } return STOP_RECURSION; } @@ -445,7 +450,7 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { 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; + float heightScale = size / info.size; glm::vec3 center = (_edit.position - info.minimum) * heightScale; float scaledRadius = _edit.radius * heightScale; @@ -461,6 +466,7 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { 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(); + bool changed = false; 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) { @@ -469,13 +475,15 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { dest[0] = red; dest[1] = green; dest[2] = blue; + changed = true; } } lineDest += stride; } - - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + if (changed) { + HeightfieldDataPointer newPointer(new HeightfieldData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + } return STOP_RECURSION; } diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index 2b46330961..e6b96e97b0 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -164,6 +164,11 @@ Box::Box(const glm::vec3& minimum, const glm::vec3& maximum) : minimum(minimum), maximum(maximum) { } +void Box::add(const Box& other) { + minimum = glm::min(minimum, other.minimum); + maximum = glm::max(maximum, other.maximum); +} + bool Box::contains(const glm::vec3& point) const { return point.x >= minimum.x && point.x <= maximum.x && point.y >= minimum.y && point.y <= maximum.y && @@ -182,6 +187,14 @@ bool Box::intersects(const Box& other) const { other.maximum.z >= minimum.z && other.minimum.z <= maximum.z; } +Box Box::getIntersection(const Box& other) const { + return Box(glm::max(minimum, other.minimum), glm::min(maximum, other.maximum)); +} + +bool Box::isEmpty() const { + return minimum.x >= maximum.x || minimum.y >= maximum.y || minimum.z >= maximum.z; +} + const int X_MAXIMUM_FLAG = 1; const int Y_MAXIMUM_FLAG = 2; const int Z_MAXIMUM_FLAG = 4; diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index 339c07a21e..4228af059f 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -44,12 +44,18 @@ public: explicit Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3()); + void add(const Box& other); + bool contains(const glm::vec3& point) const; bool contains(const Box& other) const; bool intersects(const Box& other) const; + Box getIntersection(const Box& other) const; + + bool isEmpty() const; + float getLongestSide() const { return qMax(qMax(maximum.x - minimum.x, maximum.y - minimum.y), maximum.z - minimum.z); } glm::vec3 getVertex(int index) const;