From 131486c8f7febf320f40fbabc4d5e5220753479b Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 16 Oct 2014 18:53:42 -0700 Subject: [PATCH] More progress on voxelizing heightfields. --- libraries/metavoxels/src/MetavoxelData.cpp | 184 +++++++++ libraries/metavoxels/src/MetavoxelData.h | 28 ++ .../metavoxels/src/MetavoxelMessages.cpp | 379 ++++++------------ 3 files changed, 325 insertions(+), 266 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 920482a016..e9cb797114 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -2365,3 +2365,187 @@ bool StaticModel::findRayIntersection(const glm::vec3& origin, const glm::vec3& QByteArray StaticModel::getRendererClassName() const { return "StaticModelRenderer"; } + +const float EIGHT_BIT_MAXIMUM = 255.0f; + +Heightfield::Heightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color, + const QByteArray& material, const QVector& materials) : + _increment(increment), + _width((int)glm::round((bounds.maximum.x - bounds.minimum.x) / increment) + 1), + _heightScale((bounds.maximum.y - bounds.minimum.y) / EIGHT_BIT_MAXIMUM), + _height(height), + _color(color), + _material(material), + _materials(materials) { + + setBounds(bounds); +} + +bool Heightfield::contains(const glm::vec3& point) { + if (!getBounds().contains(point)) { + return false; + } + glm::vec3 relative = (point - getBounds().minimum) / _increment; + glm::vec3 floors = glm::floor(relative); + glm::vec3 ceils = glm::ceil(relative); + glm::vec3 fracts = glm::fract(relative); + int floorX = (int)floors.x; + int floorZ = (int)floors.z; + int ceilX = (int)ceils.x; + int ceilZ = (int)ceils.z; + const uchar* src = (const uchar*)_height.constData(); + float upperLeft = src[floorZ * _width + floorX]; + float lowerRight = src[ceilZ * _width + 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 * _width + ceilX]; + interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z), + (fracts.x - fracts.z) / (1.0f - fracts.z)); + + } else { + float lowerLeft = src[ceilZ * _width + floorX]; + interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z); + } + return interpolatedHeight != 0.0f && point.y <= interpolatedHeight * _heightScale + getBounds().minimum.y; +} + +bool Heightfield::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { + // find the initial location in heightfield coordinates + float rayDistance; + glm::vec3 direction = end - start; + if (!getBounds().findRayIntersection(start, direction, rayDistance) || rayDistance > 1.0f) { + return false; + } + glm::vec3 entry = (start + direction * rayDistance - getBounds().minimum) / _increment; + direction /= _increment; + 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; + const uchar* src = (const uchar*)_height.constData(); + int highestX = _width - 1; + float highestY = (getBounds().maximum.y - getBounds().minimum.y) / _increment; + int highestZ = (int)glm::round((getBounds().maximum.z - getBounds().minimum.z) / _increment); + float heightScale = _heightScale / _increment; + while (withinBounds && accumulatedDistance <= 1.0f) { + // find the heights at the corners of the current cell + int floorX = qMin(qMax((int)floors.x, 0), highestX); + int floorZ = qMin(qMax((int)floors.z, 0), highestZ); + int ceilX = qMin(qMax((int)ceils.x, 0), highestX); + int ceilZ = qMin(qMax((int)ceils.z, 0), highestZ); + float upperLeft = src[floorZ * _width + floorX] * heightScale; + float upperRight = src[floorZ * _width + ceilX] * heightScale; + float lowerLeft = src[ceilZ * _width + floorX] * heightScale; + float lowerRight = src[ceilZ * _width + 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) { + withinBounds = false; // line points upwards/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 <= highestY); + if (exitDistance == xDistance) { + if (direction.x > 0.0f) { + nextFloors.x += 1.0f; + withinBounds &= (nextCeils.x += 1.0f) <= highestX; + } 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) <= highestZ; + } 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) { + distance = rayDistance + (accumulatedDistance + planeDistance) * _increment; + normal = glm::normalize(lowerNormal); + return true; + } + } + + // 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) { + distance = rayDistance + (accumulatedDistance + planeDistance) * _increment; + normal = glm::normalize(upperNormal); + return true; + } + } + + // no joy; continue on our way + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + } + + return false; +} diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index db28ce056f..42aa3e321b 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -848,4 +848,32 @@ private: QUrl _url; }; +/// A heightfield represented as a spanner. +class Heightfield : public Transformable { + Q_OBJECT + +public: + + Heightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color, + const QByteArray& material, const QVector& materials); + + QByteArray& getHeight() { return _height; } + QByteArray& getColor() { return _color; } + QByteArray& getMaterial() { return _material; } + QVector& getMaterials() { return _materials; } + + virtual bool contains(const glm::vec3& point); + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + +private: + + float _increment; + int _width; + float _heightScale; + QByteArray _height; + QByteArray _color; + QByteArray _material; + QVector _materials; +}; + #endif // hifi_MetavoxelData_h diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 61ec01e216..5898d7cf6f 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -625,192 +625,6 @@ VoxelMaterialSpannerEdit::VoxelMaterialSpannerEdit(const SharedObjectPointer& sp spanner(spanner) { } -class VoxelHeightfieldFetchVisitor : public MetavoxelVisitor { -public: - - VoxelHeightfieldFetchVisitor(); - - void init(const Box& bounds); - - bool isEmpty() const { return _heightContents.isEmpty(); } - - int getContentsWidth() const { return _contentsWidth; } - int getContentsHeight() const { return _contentsHeight; } - - const Box& getHeightBounds() const { return _heightBounds; } - - float getInterpolatedHeight(float x, float y) const; - - QRgb getInterpolatedColor(float x, float y) const; - - int getNearestMaterial(float x, float y) const; - - const QVector& getMaterials() const { return _materials; } - - virtual int visit(MetavoxelInfo& info); - -private: - - Box _bounds; - Box _expandedBounds; - int _contentsWidth; - int _contentsHeight; - QByteArray _heightContents; - QByteArray _colorContents; - QByteArray _materialContents; - QVector _materials; - Box _heightBounds; -}; - -VoxelHeightfieldFetchVisitor::VoxelHeightfieldFetchVisitor() : - MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << - AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << - AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute()) { -} - -void VoxelHeightfieldFetchVisitor::init(const Box& bounds) { - _expandedBounds = _bounds = bounds; - float increment = (bounds.maximum.x - bounds.minimum.x) / VOXEL_BLOCK_SIZE; - _expandedBounds.maximum.x += increment; - _expandedBounds.maximum.z += increment; - _heightContents.clear(); - _colorContents.clear(); - _materialContents.clear(); - _materials.clear(); -} - -float VoxelHeightfieldFetchVisitor::getInterpolatedHeight(float x, float y) const { - int floorX = glm::floor(x), floorY = glm::floor(y); - int ceilX = glm::ceil(x), ceilY = glm::ceil(y); - const uchar* src = (const uchar*)_heightContents.constData(); - float upperLeft = src[floorY * _contentsWidth + floorX]; - float lowerRight = src[ceilY * _contentsWidth + ceilX]; - float fractX = glm::fract(x), fractY = glm::fract(y); - float interpolatedHeight = glm::mix(upperLeft, lowerRight, fractY); - if (fractX >= fractY) { - float upperRight = src[floorY * _contentsWidth + ceilX]; - interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fractY), - (fractX - fractY) / (1.0f - fractY)); - - } else { - float lowerLeft = src[ceilY * _contentsWidth + floorX]; - interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fractY), interpolatedHeight, fractX / fractY); - } - return interpolatedHeight; -} - -QRgb VoxelHeightfieldFetchVisitor::getInterpolatedColor(float x, float y) const { - int floorX = glm::floor(x), floorY = glm::floor(y); - int ceilX = glm::ceil(x), ceilY = glm::ceil(y); - const uchar* src = (const uchar*)_colorContents.constData(); - const uchar* upperLeft = src + (floorY * _contentsWidth + floorX) * DataBlock::COLOR_BYTES; - const uchar* lowerRight = src + (ceilY * _contentsWidth + ceilX) * DataBlock::COLOR_BYTES; - float fractX = glm::fract(x), fractY = glm::fract(y); - glm::vec3 interpolatedColor = glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), - glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fractY); - if (fractX >= fractY) { - const uchar* upperRight = src + (floorY * _contentsWidth + ceilX) * DataBlock::COLOR_BYTES; - interpolatedColor = glm::mix(interpolatedColor, glm::mix(glm::vec3(upperRight[0], upperRight[1], upperRight[2]), - glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fractY), (fractX - fractY) / (1.0f - fractY)); - - } else { - const uchar* lowerLeft = src + (ceilY * _contentsWidth + floorX) * DataBlock::COLOR_BYTES; - interpolatedColor = glm::mix(glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), - glm::vec3(lowerLeft[0], lowerLeft[1], lowerLeft[2]), fractY), interpolatedColor, fractX / fractY); - } - return qRgb(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); -} - -int VoxelHeightfieldFetchVisitor::getNearestMaterial(float x, float y) const { - const uchar* src = (const uchar*)_materialContents.constData(); - return src[(int)glm::round(y) * _contentsWidth + (int)glm::round(x)]; -} - -int VoxelHeightfieldFetchVisitor::visit(MetavoxelInfo& info) { - Box bounds = info.getBounds(); - if (!bounds.intersects(_expandedBounds)) { - return STOP_RECURSION; - } - if (!info.isLeaf) { - return DEFAULT_ORDER; - } - HeightfieldHeightDataPointer heightPointer = info.inputValues.at(0).getInlineValue(); - if (!heightPointer) { - return STOP_RECURSION; - } - const QByteArray& heightContents = heightPointer->getContents(); - int size = glm::sqrt((float)heightContents.size()); - float heightIncrement = info.size / size; - - // the first heightfield we encounter determines the resolution - if (_heightContents.isEmpty()) { - _heightBounds.minimum = glm::floor(_bounds.minimum / heightIncrement) * heightIncrement; - _heightBounds.maximum = (glm::ceil(_bounds.maximum / heightIncrement) + glm::vec3(1.0f, 0.0f, 1.0f)) * heightIncrement; - _heightBounds.minimum.y = bounds.minimum.y; - _heightBounds.maximum.y = bounds.maximum.y; - _contentsWidth = (int)glm::round((_heightBounds.maximum.x - _heightBounds.minimum.x) / heightIncrement); - _contentsHeight = (int)glm::round((_heightBounds.maximum.z - _heightBounds.minimum.z) / heightIncrement); - _heightContents = QByteArray(_contentsWidth * _contentsHeight, 0); - _colorContents = QByteArray(_contentsWidth * _contentsHeight * DataBlock::COLOR_BYTES, 0); - _materialContents = QByteArray(_contentsWidth * _contentsHeight, 0); - } - - Box overlap = _heightBounds.getIntersection(bounds); - if (overlap.isEmpty()) { - return STOP_RECURSION; - } - - int srcX = (overlap.minimum.x - bounds.minimum.x) / heightIncrement; - int srcY = (overlap.minimum.z - bounds.minimum.z) / heightIncrement; - int destX = (overlap.minimum.x - _heightBounds.minimum.x) / heightIncrement; - int destY = (overlap.minimum.z - _heightBounds.minimum.z) / heightIncrement; - int width = (overlap.maximum.x - overlap.minimum.x) / heightIncrement; - int height = (overlap.maximum.z - overlap.minimum.z) / heightIncrement; - char* heightDest = _heightContents.data() + destY * _contentsWidth + destX; - const char* heightSrc = heightContents.constData() + srcY * size + srcX; - - for (int y = 0; y < height; y++, heightDest += _contentsWidth, heightSrc += size) { - memcpy(heightDest, heightSrc, width); - } - - HeightfieldColorDataPointer colorPointer = info.inputValues.at(1).getInlineValue(); - if (colorPointer && colorPointer->getContents().size() == heightContents.size() * DataBlock::COLOR_BYTES) { - char* colorDest = _colorContents.data() + (destY * _contentsWidth + destX) * DataBlock::COLOR_BYTES; - const char* colorSrc = colorPointer->getContents().constData() + (srcY * size + srcX) * DataBlock::COLOR_BYTES; - - for (int y = 0; y < height; y++, colorDest += _contentsWidth * DataBlock::COLOR_BYTES, - colorSrc += size * DataBlock::COLOR_BYTES) { - memcpy(colorDest, colorSrc, width * DataBlock::COLOR_BYTES); - } - } - - HeightfieldMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue(); - if (materialPointer && materialPointer->getContents().size() == heightContents.size()) { - uchar* materialDest = (uchar*)_materialContents.data() + destY * _contentsWidth + destX; - const uchar* materialSrc = (const uchar*)materialPointer->getContents().constData() + srcY * size + srcX; - const QVector& materials = materialPointer->getMaterials(); - QHash materialMap; - - for (int y = 0; y < height; y++, materialDest += _contentsWidth, materialSrc += size) { - const uchar* lineSrc = materialSrc; - for (uchar* lineDest = materialDest, *end = lineDest + width; lineDest != end; lineDest++, lineSrc++) { - int value = *lineSrc; - if (value == 0) { - continue; - } - int& mapping = materialMap[value]; - if (mapping == 0) { - _materials.append(materials.at(value - 1)); - mapping = _materials.size(); - } - *lineDest = mapping; - } - } - } - - return STOP_RECURSION; -} - class VoxelMaterialSpannerEditVisitor : public MetavoxelVisitor { public: @@ -824,7 +638,6 @@ private: SharedObjectPointer _material; QColor _color; float _blockSize; - VoxelHeightfieldFetchVisitor _heightfieldVisitor; }; VoxelMaterialSpannerEditVisitor::VoxelMaterialSpannerEditVisitor(Spanner* spanner, @@ -849,14 +662,12 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) { if (info.size > _blockSize) { return DEFAULT_ORDER; } - bool fetchFromHeightfield = false; QVector oldColorContents; VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue(); if (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) { oldColorContents = colorPointer->getContents(); } else { oldColorContents = QVector(VOXEL_BLOCK_VOLUME); - fetchFromHeightfield = true; } QVector hermiteContents; @@ -865,7 +676,6 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) { hermiteContents = hermitePointer->getContents(); } else { hermiteContents = QVector(VOXEL_BLOCK_VOLUME * VoxelHermiteData::EDGE_COUNT); - fetchFromHeightfield = true; } QByteArray materialContents; @@ -876,68 +686,9 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) { materials = materialPointer->getMaterials(); } else { materialContents = QByteArray(VOXEL_BLOCK_VOLUME, 0); - fetchFromHeightfield = true; } float scale = VOXEL_BLOCK_SIZE / info.size; - if (fetchFromHeightfield) { - // the first time we touch a voxel block, we have to look up any intersecting heightfield data - _heightfieldVisitor.init(bounds); - _data->guide(_heightfieldVisitor); - if (!_heightfieldVisitor.isEmpty()) { - QRgb* colorDest = oldColorContents.data(); - QRgb* hermiteDest = hermiteContents.data(); - uchar* materialDest = (uchar*)materialContents.data(); - const Box& heightBounds = _heightfieldVisitor.getHeightBounds(); - float heightStepX = (heightBounds.maximum.x - heightBounds.minimum.x) / - (_heightfieldVisitor.getContentsWidth() - 1); - float heightStepY = (heightBounds.maximum.z - heightBounds.minimum.z) / - (_heightfieldVisitor.getContentsHeight() - 1); - float lineHeightX = (bounds.minimum.x - heightBounds.minimum.x) / heightStepX; - float heightY = (bounds.minimum.z - heightBounds.minimum.z) / heightStepY; - float heightScale = (heightBounds.maximum.y - heightBounds.minimum.y) / EIGHT_BIT_MAXIMUM; - QHash materialMap; - - for (int z = 0; z < VOXEL_BLOCK_SAMPLES; z++, heightY += heightStepY) { - float heightX = lineHeightX; - for (int x = 0; x < VOXEL_BLOCK_SAMPLES; x++, colorDest++, hermiteDest += VoxelHermiteData::EDGE_COUNT, - materialDest++, heightX += heightStepX) { - float height = _heightfieldVisitor.getInterpolatedHeight(heightX, heightY); - if (height == 0.0f) { - continue; - } - height = heightBounds.minimum.y + height * heightScale; - if (height < bounds.minimum.y) { - continue; - } - height = (height - bounds.minimum.y) * scale; - int clampedHeight = qMin((int)height, VOXEL_BLOCK_SIZE); - QRgb color = _heightfieldVisitor.getInterpolatedColor(heightX, heightY); - uchar* lineMaterialDest = materialDest; - uchar material = _heightfieldVisitor.getNearestMaterial(heightX, heightY); - if (material != 0) { - int& mapping = materialMap[material]; - if (mapping == 0) { - materials.append(_heightfieldVisitor.getMaterials().at(material - 1)); - mapping = materials.size(); - } - material = mapping; - } - for (QRgb* lineColorDest = colorDest, *end = lineColorDest + clampedHeight * VOXEL_BLOCK_SAMPLES; - lineColorDest != end; lineColorDest += VOXEL_BLOCK_SAMPLES, - lineMaterialDest += VOXEL_BLOCK_SAMPLES) { - *lineColorDest = color; - *lineMaterialDest = material; - } - hermiteDest[clampedHeight * VOXEL_BLOCK_SAMPLES * VoxelHermiteData::EDGE_COUNT + 1] = packNormal( - glm::vec3(0.0f, 1.0f, 0.0f), (height - clampedHeight) * EIGHT_BIT_MAXIMUM); - } - colorDest += VOXEL_BLOCK_AREA - VOXEL_BLOCK_SAMPLES; - hermiteDest += (VOXEL_BLOCK_AREA - VOXEL_BLOCK_SAMPLES) * VoxelHermiteData::EDGE_COUNT; - materialDest += VOXEL_BLOCK_AREA - VOXEL_BLOCK_SAMPLES; - } - } - } QVector colorContents = oldColorContents; Box overlap = info.getBounds().getIntersection(_spanner->getBounds()); @@ -1100,19 +851,26 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } -class HeightfieldClearVisitor : public MetavoxelVisitor { +class HeightfieldClearFetchVisitor : public MetavoxelVisitor { public: - HeightfieldClearVisitor(const Box& bounds, float granularity); + HeightfieldClearFetchVisitor(const Box& bounds, float granularity); + + const SharedObjectPointer& getSpanner() const { return _spanner; } virtual int visit(MetavoxelInfo& info); private: Box _bounds; + Box _expandedBounds; + SharedObjectPointer _spanner; + Box _spannerBounds; + int _heightfieldWidth; + int _heightfieldHeight; }; -HeightfieldClearVisitor::HeightfieldClearVisitor(const Box& bounds, float granularity) : +HeightfieldClearFetchVisitor::HeightfieldClearFetchVisitor(const Box& bounds, float granularity) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), QVector() << @@ -1126,14 +884,15 @@ HeightfieldClearVisitor::HeightfieldClearVisitor(const Box& bounds, float granul _bounds.maximum = glm::ceil(bounds.maximum / nodeSize) * nodeSize; // expand to include edges + _expandedBounds = _bounds; float increment = nodeSize / VOXEL_BLOCK_SIZE; - _bounds.maximum.x += increment; - _bounds.maximum.z += increment; + _expandedBounds.maximum.x += increment; + _expandedBounds.maximum.z += increment; } -int HeightfieldClearVisitor::visit(MetavoxelInfo& info) { +int HeightfieldClearFetchVisitor::visit(MetavoxelInfo& info) { Box bounds = info.getBounds(); - if (!bounds.intersects(_bounds)) { + if (!bounds.intersects(_expandedBounds)) { return STOP_RECURSION; } if (!info.isLeaf) { @@ -1147,17 +906,97 @@ int HeightfieldClearVisitor::visit(MetavoxelInfo& info) { int size = glm::sqrt((float)contents.size()); float heightScale = size / info.size; - Box overlap = bounds.getIntersection(_bounds); - int destX = (overlap.minimum.x - info.minimum.x) * heightScale; - int destY = (overlap.minimum.z - info.minimum.z) * heightScale; - int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) * heightScale); - int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) * heightScale); - char* dest = contents.data() + destY * size + destX; + Box overlap = bounds.getIntersection(_expandedBounds); + int srcX = (overlap.minimum.x - info.minimum.x) * heightScale; + int srcY = (overlap.minimum.z - info.minimum.z) * heightScale; + int srcWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) * heightScale); + int srcHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) * heightScale); + char* src = contents.data() + srcY * size + srcX; + + // check for non-zero values + bool foundNonZero = false; + for (int y = 0; y < srcHeight; y++, src += (size - srcWidth)) { + for (char* end = src + srcWidth; src != end; src++) { + if (*src != 0) { + foundNonZero = true; + goto outerBreak; + } + } + } + outerBreak: + + // if everything is zero, we're done + if (!foundNonZero) { + return STOP_RECURSION; + } + + // create spanner if necessary + Heightfield* spanner = static_cast(_spanner.data()); + float increment = 1.0f / heightScale; + if (!spanner) { + _spannerBounds.minimum = glm::floor(_bounds.minimum / increment) * increment; + _spannerBounds.maximum = glm::ceil(_bounds.maximum / increment) * increment; + _spannerBounds.minimum.y = bounds.minimum.y; + _spannerBounds.maximum.y = bounds.maximum.y; + _heightfieldWidth = (int)glm::round((_spannerBounds.maximum.x - _spannerBounds.minimum.x) / increment) + 1; + _heightfieldHeight = (int)glm::round((_spannerBounds.maximum.z - _spannerBounds.minimum.z) / increment) + 1; + int heightfieldArea = _heightfieldWidth * _heightfieldHeight; + _spanner = spanner = new Heightfield(_spannerBounds, increment, QByteArray(heightfieldArea, 0), + QByteArray(heightfieldArea * DataBlock::COLOR_BYTES, 0), QByteArray(heightfieldArea, 0), + QVector()); + } + + // copy the inner area + overlap = bounds.getIntersection(_spannerBounds); + int destX = (overlap.minimum.x - _spannerBounds.minimum.x) * heightScale; + int destY = (overlap.minimum.z - _spannerBounds.minimum.z) * heightScale; + int destWidth = (int)glm::round((overlap.maximum.x - overlap.minimum.x) * heightScale) + 1; + int destHeight = (int)glm::round((overlap.maximum.z - overlap.minimum.z) * heightScale) + 1; + char* dest = spanner->getHeight().data() + destY * _heightfieldWidth + destX; + srcX = (overlap.minimum.x - info.minimum.x) * heightScale; + srcY = (overlap.minimum.z - info.minimum.z) * heightScale; + src = contents.data() + srcY * size + srcX; + + for (int y = 0; y < destHeight; y++, dest += _heightfieldWidth, src += size) { + memcpy(dest, src, destWidth); + } + + // clear the inner area + Box innerBounds = _bounds; + innerBounds.minimum.x += increment; + innerBounds.minimum.z += increment; + overlap = bounds.getIntersection(innerBounds); + destX = (overlap.minimum.x - info.minimum.x) * heightScale; + destY = (overlap.minimum.z - info.minimum.z) * heightScale; + destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) * heightScale); + destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) * heightScale); + dest = contents.data() + destY * size + destX; for (int y = 0; y < destHeight; y++, dest += size) { memset(dest, 0, destWidth); } + // see if there are any non-zero values left + foundNonZero = false; + dest = contents.data(); + for (char* end = dest + contents.size(); dest != end; dest++) { + if (*dest != 0) { + foundNonZero = true; + break; + } + } + + // if all is gone, clear the node + if (!foundNonZero) { + info.outputValues[0] = AttributeValue(_outputs.at(0), + encodeInline(HeightfieldHeightDataPointer())); + info.outputValues[1] = AttributeValue(_outputs.at(1), + encodeInline(HeightfieldColorDataPointer())); + info.outputValues[2] = AttributeValue(_outputs.at(2), + encodeInline(HeightfieldMaterialDataPointer())); + return STOP_RECURSION; + } + HeightfieldHeightDataPointer newHeightPointer(new HeightfieldHeightData(contents)); info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newHeightPointer)); @@ -1216,12 +1055,20 @@ void VoxelMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObject // make sure it's either 100% transparent or 100% opaque QColor color = averageColor; color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); + + // clear/fetch any heightfield data + HeightfieldClearFetchVisitor heightfieldVisitor(spanner->getBounds(), spanner->getVoxelizationGranularity()); + data.guide(heightfieldVisitor); + + // voxelize the fetched heightfield, if any + if (heightfieldVisitor.getSpanner()) { + VoxelMaterialSpannerEditVisitor visitor(static_cast(heightfieldVisitor.getSpanner().data()), + material, color); + data.guide(visitor); + } + VoxelMaterialSpannerEditVisitor visitor(spanner, material, color); data.guide(visitor); - - // clear any heightfield data - HeightfieldClearVisitor clearVisitor(spanner->getBounds(), spanner->getVoxelizationGranularity()); - data.guide(clearVisitor); } PaintVoxelMaterialEdit::PaintVoxelMaterialEdit(const glm::vec3& position, float radius,