Merge pull request #3290 from ey6es/metavoxels

Big change in the way shared borders are handled in order to fix seams between blocks.  Fixes for height/ray queries.  Minimal rectangles for deltas, switch between PNG and deflated JPG encodings.
This commit is contained in:
Philip Rosedale 2014-08-15 07:57:46 -07:00
commit 28abc6ab2a
12 changed files with 1046 additions and 435 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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<AttributePointer>() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>(), 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<HeightfieldBuffer*>(
info.inputValues.at(0).getInlineValue<BufferDataPointer>().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<AttributePointer>() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>(), 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<HeightfieldBuffer*>(
info.inputValues.at(0).getInlineValue<BufferDataPointer>().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<AttributePointer>() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>(), 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<HeightfieldPoint> 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<int> 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<BufferDataPointer>(children[0]);
for (int i = 1; i < MERGE_COUNT; i++) {
if (firstChild != decodeInline<BufferDataPointer>(children[i])) {
*(BufferDataPointer*)&parent = _defaultValue;
*(BufferDataPointer*)&parent = _defaultValue;
for (int i = 0; i < MERGE_COUNT; i++) {
if (decodeInline<BufferDataPointer>(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<Box>& intersections);
void init(HeightfieldBuffer* buffer) { _buffer = buffer; }
virtual int visit(MetavoxelInfo& info);
private:
const QVector<Box>& _intersections;
HeightfieldBuffer* _buffer;
};
HeightfieldAugmentVisitor::HeightfieldAugmentVisitor(const MetavoxelLOD& lod) :
HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector<Box>& intersections) :
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector<AttributePointer>() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod) {
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector<AttributePointer>(), lod),
_intersections(intersections) {
}
int HeightfieldAugmentVisitor::visit(MetavoxelInfo& info) {
if (info.isLeaf) {
HeightfieldBuffer* buffer = NULL;
HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldDataPointer>();
if (height) {
HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldDataPointer>();
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<HeightfieldDataPointer>();
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<HeightfieldDataPointer>();
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<Box> regions;
Box regionBounds;
HeightfieldRegionVisitor(const MetavoxelLOD& lod);
virtual int visit(MetavoxelInfo& info);
private:
void addRegion(const Box& unextended, const Box& extended);
QVector<Box> _intersections;
HeightfieldFetchVisitor _fetchVisitor;
};
HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) :
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldColorAttribute() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>() <<
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<HeightfieldDataPointer>();
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<HeightfieldDataPointer>();
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<const HeightfieldBuffer*>(
info.inputValues.at(2).getInlineValue<BufferDataPointer>().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<Box>& regions, const Box& regionBounds);
virtual int visit(MetavoxelInfo& info);
private:
const QVector<Box>& _regions;
const Box& _regionBounds;
QVector<Box> _intersections;
HeightfieldFetchVisitor _fetchVisitor;
};
HeightfieldUpdateVisitor::HeightfieldUpdateVisitor(const MetavoxelLOD& lod, const QVector<Box>& regions,
const Box& regionBounds) :
MetavoxelVisitor(QVector<AttributePointer>() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>() <<
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<const HeightfieldBuffer*>(
info.inputValues.at(0).getInlineValue<BufferDataPointer>().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<AttributePointer>() << attribute, QVector<AttributePointer>(), lod),
BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute) :
MetavoxelVisitor(QVector<AttributePointer>() << 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<BufferDataPointer>();
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 };

View file

@ -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<QOpenGLBuffer, QOpenGLBuffer> BufferPair;
static QHash<int, BufferPair> _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.

View file

@ -956,11 +956,11 @@ void ImportHeightfieldTool::apply() {
HeightfieldBuffer* buffer = static_cast<HeightfieldBuffer*>(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)));
}
}
}

View file

@ -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<HeightfieldDataPointer>(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<HeightfieldDataPointer>(value);
if (data) {
data->write(out, false);
} else {
out << 0;
}
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(reference), false));
}
}
void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const {
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(value);
if (data) {
data->write(out, true);
} else {
out << 0;
}
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(reference), true));
}
}
void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const {
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(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;

View file

@ -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<HeightfieldData> 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<HeightfieldData> HeightfieldDataPointer;
/// An attribute that stores heightfield data.
class HeightfieldAttribute : public InlineAttribute<HeightfieldDataPointer> {
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;
};

View file

@ -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<AttributePointer>() <<
AttributeRegistry::getInstance()->getHeightfieldAttribute(), QVector<AttributePointer>(), 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<HeightfieldDataPointer>();
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<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute(),
QVector<AttributePointer>(), 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<HeightfieldDataPointer>();
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();
}

View file

@ -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();

View file

@ -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<HeightfieldDataPointer>(newPointer));
if (changed) {
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(newPointer));
if (changed) {
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(newPointer));
}
return STOP_RECURSION;
}

View file

@ -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;

View file

@ -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;